@mrciphersmith/keryx 0.3.2 → 0.3.5

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 (164) hide show
  1. package/dist/cli.js +4745 -2482
  2. package/dist/core.js +66 -10
  3. package/package.json +1 -1
  4. package/src/gdskills/bundled/install-manifest.json +578 -2
  5. package/src/gdskills/bundled/rules/core/model-selection.mdc +18 -0
  6. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +1 -1
  7. package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.md +1 -1
  8. package/src/gdskills/bundled/skills/planning/interviewer/SKILL.md +1 -1
  9. package/src/gdskills/bundled/skills/quality/deploy/SKILL.md +1 -1
  10. package/src/gdskills/bundled/skills/review/review-jev-contract/SKILL.md +193 -0
  11. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +81 -21
  12. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +4 -4
  13. package/src/gdskills/bundled/stacks/c-cpp/agent-refs.json +4 -0
  14. package/src/gdskills/bundled/stacks/c-cpp/governance/eval.json +1777 -0
  15. package/src/gdskills/bundled/stacks/c-cpp/governance/scout.json +31 -0
  16. package/src/gdskills/bundled/stacks/c-cpp/pack.json +42 -0
  17. package/src/gdskills/bundled/stacks/c-cpp/rules/coding-style.mdc +80 -0
  18. package/src/gdskills/bundled/stacks/c-cpp/rules/patterns.mdc +87 -0
  19. package/src/gdskills/bundled/stacks/c-cpp/rules/security.mdc +90 -0
  20. package/src/gdskills/bundled/stacks/c-cpp/rules/testing.mdc +83 -0
  21. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-build-fix/SKILL.md +153 -0
  22. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-build-fix/evals.json +74 -0
  23. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-code-review/SKILL.md +132 -0
  24. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-code-review/evals.json +73 -0
  25. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-implementation/SKILL.md +151 -0
  26. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-implementation/evals.json +74 -0
  27. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-testing/SKILL.md +152 -0
  28. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-testing/evals.json +74 -0
  29. package/src/gdskills/bundled/stacks/ci-github-gitlab/agent-refs.json +4 -0
  30. package/src/gdskills/bundled/stacks/ci-github-gitlab/governance/eval.json +1295 -0
  31. package/src/gdskills/bundled/stacks/ci-github-gitlab/governance/scout.json +26 -0
  32. package/src/gdskills/bundled/stacks/ci-github-gitlab/pack.json +41 -0
  33. package/src/gdskills/bundled/stacks/ci-github-gitlab/rules/patterns.mdc +77 -0
  34. package/src/gdskills/bundled/stacks/ci-github-gitlab/rules/security.mdc +144 -0
  35. package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-build-fix/SKILL.md +121 -0
  36. package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-build-fix/evals.json +73 -0
  37. package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-code-review/SKILL.md +139 -0
  38. package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-code-review/evals.json +73 -0
  39. package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-implementation/SKILL.md +147 -0
  40. package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-implementation/evals.json +74 -0
  41. package/src/gdskills/bundled/stacks/csharp-dotnet/agent-refs.json +4 -0
  42. package/src/gdskills/bundled/stacks/csharp-dotnet/governance/eval.json +1881 -0
  43. package/src/gdskills/bundled/stacks/csharp-dotnet/governance/scout.json +33 -0
  44. package/src/gdskills/bundled/stacks/csharp-dotnet/pack.json +38 -0
  45. package/src/gdskills/bundled/stacks/csharp-dotnet/rules/coding-style.mdc +100 -0
  46. package/src/gdskills/bundled/stacks/csharp-dotnet/rules/patterns.mdc +107 -0
  47. package/src/gdskills/bundled/stacks/csharp-dotnet/rules/security.mdc +86 -0
  48. package/src/gdskills/bundled/stacks/csharp-dotnet/rules/testing.mdc +89 -0
  49. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-build-fix/SKILL.md +143 -0
  50. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-build-fix/evals.json +77 -0
  51. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-code-review/SKILL.md +121 -0
  52. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-code-review/evals.json +77 -0
  53. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-implementation/SKILL.md +134 -0
  54. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-implementation/evals.json +76 -0
  55. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-testing/SKILL.md +130 -0
  56. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-testing/evals.json +77 -0
  57. package/src/gdskills/bundled/stacks/docker-k8s-terraform/agent-refs.json +4 -0
  58. package/src/gdskills/bundled/stacks/docker-k8s-terraform/governance/eval.json +865 -0
  59. package/src/gdskills/bundled/stacks/docker-k8s-terraform/governance/scout.json +16 -0
  60. package/src/gdskills/bundled/stacks/docker-k8s-terraform/pack.json +46 -0
  61. package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/coding-style.mdc +74 -0
  62. package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/patterns.mdc +81 -0
  63. package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/security.mdc +146 -0
  64. package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/testing.mdc +61 -0
  65. package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-build-fix/SKILL.md +151 -0
  66. package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-build-fix/evals.json +74 -0
  67. package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-review/SKILL.md +135 -0
  68. package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-review/evals.json +76 -0
  69. package/src/gdskills/bundled/stacks/flutter-dart/agent-refs.json +4 -0
  70. package/src/gdskills/bundled/stacks/flutter-dart/governance/eval.json +1849 -0
  71. package/src/gdskills/bundled/stacks/flutter-dart/governance/scout.json +33 -0
  72. package/src/gdskills/bundled/stacks/flutter-dart/pack.json +41 -0
  73. package/src/gdskills/bundled/stacks/flutter-dart/rules/coding-style.mdc +98 -0
  74. package/src/gdskills/bundled/stacks/flutter-dart/rules/patterns.mdc +88 -0
  75. package/src/gdskills/bundled/stacks/flutter-dart/rules/security.mdc +91 -0
  76. package/src/gdskills/bundled/stacks/flutter-dart/rules/testing.mdc +101 -0
  77. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-build-fix/SKILL.md +134 -0
  78. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-build-fix/evals.json +79 -0
  79. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-code-review/SKILL.md +124 -0
  80. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-code-review/evals.json +74 -0
  81. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-implementation/SKILL.md +139 -0
  82. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-implementation/evals.json +77 -0
  83. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-testing/SKILL.md +134 -0
  84. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-testing/evals.json +74 -0
  85. package/src/gdskills/bundled/stacks/kotlin-android/agent-refs.json +4 -0
  86. package/src/gdskills/bundled/stacks/kotlin-android/governance/eval.json +1889 -0
  87. package/src/gdskills/bundled/stacks/kotlin-android/governance/scout.json +34 -0
  88. package/src/gdskills/bundled/stacks/kotlin-android/pack.json +38 -0
  89. package/src/gdskills/bundled/stacks/kotlin-android/rules/coding-style.mdc +89 -0
  90. package/src/gdskills/bundled/stacks/kotlin-android/rules/patterns.mdc +96 -0
  91. package/src/gdskills/bundled/stacks/kotlin-android/rules/security.mdc +90 -0
  92. package/src/gdskills/bundled/stacks/kotlin-android/rules/testing.mdc +89 -0
  93. package/src/gdskills/bundled/stacks/kotlin-android/skills/compose-implementation/SKILL.md +150 -0
  94. package/src/gdskills/bundled/stacks/kotlin-android/skills/compose-implementation/evals.json +77 -0
  95. package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-build-fix/SKILL.md +151 -0
  96. package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-build-fix/evals.json +76 -0
  97. package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-code-review/SKILL.md +139 -0
  98. package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-code-review/evals.json +78 -0
  99. package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-testing/SKILL.md +131 -0
  100. package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-testing/evals.json +77 -0
  101. package/src/gdskills/bundled/stacks/php-laravel/agent-refs.json +4 -0
  102. package/src/gdskills/bundled/stacks/php-laravel/governance/eval.json +1829 -0
  103. package/src/gdskills/bundled/stacks/php-laravel/governance/scout.json +33 -0
  104. package/src/gdskills/bundled/stacks/php-laravel/pack.json +41 -0
  105. package/src/gdskills/bundled/stacks/php-laravel/rules/coding-style.mdc +82 -0
  106. package/src/gdskills/bundled/stacks/php-laravel/rules/patterns.mdc +80 -0
  107. package/src/gdskills/bundled/stacks/php-laravel/rules/security.mdc +80 -0
  108. package/src/gdskills/bundled/stacks/php-laravel/rules/testing.mdc +82 -0
  109. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-build-fix/SKILL.md +143 -0
  110. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-build-fix/evals.json +74 -0
  111. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-code-review/SKILL.md +126 -0
  112. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-code-review/evals.json +76 -0
  113. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-implementation/SKILL.md +140 -0
  114. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-implementation/evals.json +75 -0
  115. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-testing/SKILL.md +124 -0
  116. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-testing/evals.json +74 -0
  117. package/src/gdskills/bundled/stacks/ruby-rails/agent-refs.json +4 -0
  118. package/src/gdskills/bundled/stacks/ruby-rails/governance/eval.json +1673 -0
  119. package/src/gdskills/bundled/stacks/ruby-rails/governance/scout.json +33 -0
  120. package/src/gdskills/bundled/stacks/ruby-rails/pack.json +42 -0
  121. package/src/gdskills/bundled/stacks/ruby-rails/rules/coding-style.mdc +69 -0
  122. package/src/gdskills/bundled/stacks/ruby-rails/rules/patterns.mdc +93 -0
  123. package/src/gdskills/bundled/stacks/ruby-rails/rules/security.mdc +90 -0
  124. package/src/gdskills/bundled/stacks/ruby-rails/rules/testing.mdc +89 -0
  125. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-build-fix/SKILL.md +143 -0
  126. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-build-fix/evals.json +73 -0
  127. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-code-review/SKILL.md +134 -0
  128. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-code-review/evals.json +71 -0
  129. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-implementation/SKILL.md +141 -0
  130. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-implementation/evals.json +72 -0
  131. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-testing/SKILL.md +125 -0
  132. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-testing/evals.json +72 -0
  133. package/src/gdskills/bundled/stacks/sql-db/agent-refs.json +4 -0
  134. package/src/gdskills/bundled/stacks/sql-db/governance/eval.json +1829 -0
  135. package/src/gdskills/bundled/stacks/sql-db/governance/scout.json +30 -0
  136. package/src/gdskills/bundled/stacks/sql-db/pack.json +40 -0
  137. package/src/gdskills/bundled/stacks/sql-db/rules/coding-style.mdc +69 -0
  138. package/src/gdskills/bundled/stacks/sql-db/rules/patterns.mdc +134 -0
  139. package/src/gdskills/bundled/stacks/sql-db/rules/security.mdc +74 -0
  140. package/src/gdskills/bundled/stacks/sql-db/rules/testing.mdc +83 -0
  141. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-build-fix/SKILL.md +147 -0
  142. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-build-fix/evals.json +72 -0
  143. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-code-review/SKILL.md +132 -0
  144. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-code-review/evals.json +73 -0
  145. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-implementation/SKILL.md +153 -0
  146. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-implementation/evals.json +77 -0
  147. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-testing/SKILL.md +129 -0
  148. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-testing/evals.json +73 -0
  149. package/src/gdskills/bundled/stacks/swift-ios/agent-refs.json +4 -0
  150. package/src/gdskills/bundled/stacks/swift-ios/governance/eval.json +1803 -0
  151. package/src/gdskills/bundled/stacks/swift-ios/governance/scout.json +32 -0
  152. package/src/gdskills/bundled/stacks/swift-ios/pack.json +38 -0
  153. package/src/gdskills/bundled/stacks/swift-ios/rules/coding-style.mdc +92 -0
  154. package/src/gdskills/bundled/stacks/swift-ios/rules/patterns.mdc +112 -0
  155. package/src/gdskills/bundled/stacks/swift-ios/rules/security.mdc +78 -0
  156. package/src/gdskills/bundled/stacks/swift-ios/rules/testing.mdc +90 -0
  157. package/src/gdskills/bundled/stacks/swift-ios/skills/swift-build-fix/SKILL.md +144 -0
  158. package/src/gdskills/bundled/stacks/swift-ios/skills/swift-build-fix/evals.json +75 -0
  159. package/src/gdskills/bundled/stacks/swift-ios/skills/swift-code-review/SKILL.md +122 -0
  160. package/src/gdskills/bundled/stacks/swift-ios/skills/swift-code-review/evals.json +75 -0
  161. package/src/gdskills/bundled/stacks/swift-ios/skills/swift-testing/SKILL.md +131 -0
  162. package/src/gdskills/bundled/stacks/swift-ios/skills/swift-testing/evals.json +75 -0
  163. package/src/gdskills/bundled/stacks/swift-ios/skills/swiftui-implementation/SKILL.md +149 -0
  164. package/src/gdskills/bundled/stacks/swift-ios/skills/swiftui-implementation/evals.json +76 -0
@@ -0,0 +1,1849 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "reports": [
4
+ {
5
+ "schemaVersion": "1.0.0",
6
+ "skillId": "flutter-dart/flutter-implementation",
7
+ "strictness": "high",
8
+ "trials": 10,
9
+ "triggerAccuracy": {
10
+ "truePositive": 6,
11
+ "falsePositive": 2,
12
+ "positives": 7,
13
+ "negatives": 7
14
+ },
15
+ "evidence": "authored",
16
+ "scenarios": [
17
+ {
18
+ "id": "trigger-positive-1",
19
+ "kind": "trigger-positive",
20
+ "prompt": "Build a screen in this Flutter app that loads orders from a repository and shows a SnackBar when the load finishes",
21
+ "strictness": "high",
22
+ "trials": 1,
23
+ "passes": 1,
24
+ "passRate": 1,
25
+ "passAtK": 1,
26
+ "grader": "trigger-rank-fork-family",
27
+ "status": "ran",
28
+ "deterministic": true
29
+ },
30
+ {
31
+ "id": "trigger-positive-2",
32
+ "kind": "trigger-positive",
33
+ "prompt": "Add a submit button to this Flutter form that calls an async repository method and updates the UI afterward",
34
+ "strictness": "high",
35
+ "trials": 1,
36
+ "passes": 1,
37
+ "passRate": 1,
38
+ "passAtK": 1,
39
+ "grader": "trigger-rank-fork-family",
40
+ "status": "ran",
41
+ "deterministic": true
42
+ },
43
+ {
44
+ "id": "trigger-positive-3",
45
+ "kind": "trigger-positive",
46
+ "prompt": "Wire this new Flutter screen up to the app's existing Riverpod providers",
47
+ "strictness": "high",
48
+ "trials": 1,
49
+ "passes": 1,
50
+ "passRate": 1,
51
+ "passAtK": 1,
52
+ "grader": "trigger-rank-fork-family",
53
+ "status": "ran",
54
+ "deterministic": true
55
+ },
56
+ {
57
+ "id": "trigger-positive-4",
58
+ "kind": "trigger-positive",
59
+ "prompt": "Extend this Flutter widget to show a loading spinner while a Future resolves",
60
+ "strictness": "high",
61
+ "trials": 1,
62
+ "passes": 1,
63
+ "passRate": 1,
64
+ "passAtK": 1,
65
+ "grader": "trigger-rank-fork-family",
66
+ "status": "ran",
67
+ "deterministic": true
68
+ },
69
+ {
70
+ "id": "trigger-positive-5",
71
+ "kind": "trigger-positive",
72
+ "prompt": "Add a text field with a controller to this Flutter form",
73
+ "strictness": "high",
74
+ "trials": 1,
75
+ "passes": 0,
76
+ "passRate": 0,
77
+ "passAtK": 0,
78
+ "grader": "trigger-rank-fork-family",
79
+ "status": "ran",
80
+ "deterministic": true
81
+ },
82
+ {
83
+ "id": "trigger-positive-6",
84
+ "kind": "trigger-positive",
85
+ "prompt": "Implement pull-to-refresh on this Flutter list screen backed by an async call",
86
+ "strictness": "high",
87
+ "trials": 1,
88
+ "passes": 1,
89
+ "passRate": 1,
90
+ "passAtK": 1,
91
+ "grader": "trigger-rank-fork-family",
92
+ "status": "ran",
93
+ "deterministic": true
94
+ },
95
+ {
96
+ "id": "trigger-positive-7",
97
+ "kind": "trigger-positive",
98
+ "prompt": "Add navigation to a detail screen after this Flutter list item is tapped",
99
+ "strictness": "high",
100
+ "trials": 1,
101
+ "passes": 1,
102
+ "passRate": 1,
103
+ "passAtK": 1,
104
+ "grader": "trigger-rank-fork-family",
105
+ "status": "ran",
106
+ "deterministic": true
107
+ },
108
+ {
109
+ "id": "trigger-negative-1",
110
+ "kind": "trigger-negative",
111
+ "prompt": "Add a SwiftUI view with @State that loads orders and shows an alert when done",
112
+ "strictness": "high",
113
+ "trials": 1,
114
+ "passes": 0,
115
+ "passRate": 0,
116
+ "passAtK": 0,
117
+ "grader": "trigger-rank-fork-family",
118
+ "status": "ran",
119
+ "deterministic": true
120
+ },
121
+ {
122
+ "id": "trigger-negative-2",
123
+ "kind": "trigger-negative",
124
+ "prompt": "Add a Jetpack Compose composable that loads orders from a repository and shows a Snackbar",
125
+ "strictness": "high",
126
+ "trials": 1,
127
+ "passes": 1,
128
+ "passRate": 1,
129
+ "passAtK": 1,
130
+ "grader": "trigger-rank-fork-family",
131
+ "status": "ran",
132
+ "deterministic": true
133
+ },
134
+ {
135
+ "id": "trigger-negative-3",
136
+ "kind": "trigger-negative",
137
+ "prompt": "Implement this feature in a C#/.NET minimal API service that calls a downstream client",
138
+ "strictness": "high",
139
+ "trials": 1,
140
+ "passes": 1,
141
+ "passRate": 1,
142
+ "passAtK": 1,
143
+ "grader": "trigger-rank-fork-family",
144
+ "status": "ran",
145
+ "deterministic": true
146
+ },
147
+ {
148
+ "id": "trigger-negative-4",
149
+ "kind": "trigger-negative",
150
+ "prompt": "Implement this React component with a form field and a submit handler",
151
+ "strictness": "high",
152
+ "trials": 1,
153
+ "passes": 1,
154
+ "passRate": 1,
155
+ "passAtK": 1,
156
+ "grader": "trigger-rank-fork-family",
157
+ "status": "ran",
158
+ "deterministic": true
159
+ },
160
+ {
161
+ "id": "trigger-negative-5",
162
+ "kind": "trigger-negative",
163
+ "prompt": "Review this Flutter diff for missing mounted checks",
164
+ "strictness": "high",
165
+ "trials": 1,
166
+ "passes": 1,
167
+ "passRate": 1,
168
+ "passAtK": 1,
169
+ "grader": "trigger-rank-fork-family",
170
+ "status": "ran",
171
+ "deterministic": true
172
+ },
173
+ {
174
+ "id": "trigger-negative-6",
175
+ "kind": "trigger-negative",
176
+ "prompt": "Fix this failing flutter test in the order package",
177
+ "strictness": "high",
178
+ "trials": 1,
179
+ "passes": 1,
180
+ "passRate": 1,
181
+ "passAtK": 1,
182
+ "grader": "trigger-rank-fork-family",
183
+ "status": "ran",
184
+ "deterministic": true
185
+ },
186
+ {
187
+ "id": "trigger-negative-7",
188
+ "kind": "trigger-negative",
189
+ "prompt": "Write widget tests for this Flutter screen's loading state",
190
+ "strictness": "high",
191
+ "trials": 1,
192
+ "passes": 0,
193
+ "passRate": 0,
194
+ "passAtK": 0,
195
+ "grader": "trigger-rank-fork-family",
196
+ "status": "ran",
197
+ "deterministic": true
198
+ },
199
+ {
200
+ "id": "buildcontext-mounted-guard",
201
+ "kind": "behavior",
202
+ "prompt": "I'm showing a SnackBar with the result after an awaited repository call finishes inside this Flutter button's onPressed handler. What do I need to watch out for?",
203
+ "strictness": "high",
204
+ "trials": 10,
205
+ "passes": 10,
206
+ "passRate": 1,
207
+ "passAtK": 1,
208
+ "grader": "regex+judge",
209
+ "status": "ran",
210
+ "trialRecords": [
211
+ {
212
+ "output": "The core hazard: that `onPressed` callback holds a `BuildContext` (the button's context) and after `await` returns, the widget that context belongs to may no longer be mounted — the user navigated away, or the screen was rebuilt/disposed while the repository call was in flight. Then `ScaffoldMessenger.of(context).showSnackBar(...)` either throws or targets a stale/deactivated context.\n\nWhat to do:\n\n1. Check `context.mounted` (Flutter 3.7+) immediately after the `await`, before touching `context` at all:\n\n```dart\nonPressed: () async {\n final result = await repository.submit(input);\n if (!context.mounted) return;\n ScaffoldMessenger.of(context).showSnackBar(\n SnackBar(content: Text(result.message)),\n );\n},\n```\n\nDo this right after the `await`, not inside a broad `try`/`catch`, so it's obvious what it's guarding.\n\n2. Decide whether the SnackBar should be tied to this widget's context or a surviving one. If the flow should still show feedback even after the screen is gone, capture `ScaffoldMessenger.of(context)` into a local *before* the `await` — the messenger lives above the route and generally survives — rather than resolving it from the now-possibly-stale context afterward. But don't do this reflexively; only when the UX actually calls for it.\n\n3. If you also call `setState()` after the await, it needs the same `mounted` check (`mounted` on the `State`, or `context.mounted`) — same failure mode, different symptom.\n\n4. Watch for overlapping taps. If the button can be pressed again while the first call is pending, you'll get out-of-order results and duplicate SnackBars. Disable the button (or guard with an in-flight flag) while the request is running.\n\n5. Only use `!` to silence analyzer complaints if the value is genuinely locally guaranteed non-null and unaffected by the intervening `await` — prefer `?.`/`??` or a real null check here.\n\n6. If the handler is `async` in a `StatelessWidget`'s build, be aware `context.mounted` is the right check; if it's a `State` method, `mounted` is equivalent and also fine. Don't rely on either being true without checking.\n\nQuick reference for this exact pattern:\n\n```dart\nonPressed: () async {\n final messenger = ScaffoldMessenger.of(context); // capture before await if feedback should survive\n setState(() => _loading = true);\n final result = await repository.submit(input);\n if (!mounted) return; // or context.mounted\n setState(() => _loading = false);\n messenger.showSnackBar(SnackBar(content: Text(result.message)));\n},\n```\n\n`flutter analyze` won't catch a missing `mounted` check for you, so this is on you to get right at every `await` that precedes a `context` or `setState` use.",
213
+ "outputSha256": "d00a37d6b5ce2def13e021c3e3fb081b2800a57f1222e3bae389a0512f89f693",
214
+ "promptSha256": "ad3f757cb2316657fe24916f43a74a651876b46ed7e46ad01767bc3b293d424d",
215
+ "deterministic": [
216
+ true
217
+ ],
218
+ "judge": {
219
+ "verdict": "pass",
220
+ "reason": "Answer states the widget/context may no longer be mounted after await (user navigated away, screen disposed) — criterion 1. Shows concrete fix `if (!context.mounted) return;` immediately after await before ScaffoldMessenger.of(context) — criterion 2. Applies it to the actual SnackBar/ScaffoldMessenger use in the prompt — criterion 3. No fail criteria: it explicitly warns against try/catch substitution."
221
+ },
222
+ "passed": true
223
+ },
224
+ {
225
+ "output": "The core hazard: `onPressed` is a synchronous callback, but you're `await`-ing inside it, so when the repository call returns, the widget that owns the button may already be gone — the user navigated away, popped the route, or the screen was otherwise disposed. At that point `ScaffoldMessenger.of(context)` (or a captured `context`) is a stale `BuildContext`, and touching it throws or targets the wrong `Scaffold`/screen.\n\nWhat to watch out for, concretely:\n\n- **Check `mounted` after the await, before touching `context`.** The idiomatic form inside a `State` is:\n\n ```dart\n Future<void> _submit() async {\n final result = await _repository.submit();\n if (!mounted) return;\n ScaffoldMessenger.of(context).showSnackBar(\n SnackBar(content: Text(result.message)),\n );\n }\n ```\n\n If you're using `context.mounted` instead (e.g. in a `StatelessWidget` or a standalone function), that works too — but still do the check *immediately after* the `await`, not wrapped in a broad `try`/`catch` or buried later in the handler.\n\n Ideally, capture the dependency you need *before* the await — `final messenger = ScaffoldMessenger.of(context);` — then after the await, still verify `mounted` and use the captured `messenger`. That way you're not re-resolving `context` after the gap at all.\n\n- **Disable the button while the request is in flight.** Otherwise a user can tap it multiple times, firing overlapping calls and (if any of them settle after the screen is gone) queuing SnackBars you don't want. A `_isSubmitting` flag guarding the `onPressed`, or disabling the button, handles this. Reset it in the same block whether or not you end up showing the SnackBar.\n\n- **Handle the error path symmetrically.** If the repository call throws, the `await` rethrows after the gap, and a `try`/`catch` that shows an error SnackBar has the exact same stale-context problem. The `mounted` check has to be in the catch path too, not just the success path. A `try`/`catch`/`finally` where the `finally` resets the loading flag is a common, clean shape — but the `mounted`-guarded UI calls go inside the success and catch branches specifically.\n\n- **Don't silently swallow the failure.** If the widget is unmounted, returning early is correct (there's nothing to show), but make sure you're not masking that with a bare `catch (_) {}` that hides genuine bugs alongside the unmounted case.\n\n- **Choose the right messenger.** `ScaffoldMessenger.of(context)` surfaces the SnackBar through the nearest `ScaffoldMessenger` (which by default persists across route changes within the same messenger scope); if this button lives in a `StatefulWidget`, keep the whole flow inside the `State` so `mounted` refers to the right thing.\n\nThe one-line rule: **after every `await`, before any `setState`, `context`, or `ScaffoldMessenger.of(context)` use, verify `mounted`/`context.mounted` — on both the success and the error path — and guard against re-entry while the call is pending.**",
226
+ "outputSha256": "6e6e5edb04229231eebe62016eab363124a65e552bd9baad253fc028e560f975",
227
+ "promptSha256": "ad3f757cb2316657fe24916f43a74a651876b46ed7e46ad01767bc3b293d424d",
228
+ "deterministic": [
229
+ true
230
+ ],
231
+ "judge": {
232
+ "verdict": "pass",
233
+ "reason": "Answer states widget may be gone after await (\"user navigated away, popped the route, or the screen was otherwise disposed\"), shows concrete fix `if (!mounted) return;` immediately after await before ScaffoldMessenger.of(context).showSnackBar, and applies it to the actual SnackBar use. No fail criteria: it explicitly rejects try/catch substitution."
234
+ },
235
+ "passed": true
236
+ },
237
+ {
238
+ "output": "The thing to watch out for is that by the time the `await` returns, the widget that owns the button may no longer be mounted — the user could have navigated away, or the screen could have been disposed for any other reason. If you then call `ScaffoldMessenger.of(context)` (or use `context` in any other way) without a check, you're using a `BuildContext` across an async gap. That's the exact pattern `use_build_context_synchronously` flags, and it can show the SnackBar on the wrong screen or throw if the context is defunct.\n\nThe fix is to re-check `mounted` (in a `State`) or `context.mounted` (a `BuildContext` extension available on the current Flutter SDK) immediately after the `await`, before touching `context`, and bail out if it's false:\n\n```dart\nonPressed: () async {\n final result = await repository.submit(); // async gap\n if (!context.mounted) return; // guard before using context\n ScaffoldMessenger.of(context).showSnackBar(\n SnackBar(content: Text('Done: ${result.status}')),\n );\n},\n```\n\nA few related points specific to the SnackBar case:\n\n- **Capture the messenger before the await if you prefer**, rather than the raw `context`. `final messenger = ScaffoldMessenger.of(context);` followed by `await ...; messenger.showSnackBar(...)` sidesteps the linter's context-across-async-gap warning because you're no longer dereferencing `context` after the gap. It still doesn't tell you the screen is gone, so if showing a SnackBar on a disposed route matters, keep a `mounted` check too. For a plain global SnackBar most apps prefer the `context.mounted` guard.\n- **Don't wrap the whole thing in a try/catch as a substitute for the guard.** A `try`/`catch` around the `await` doesn't make the `context` use safe; the guard and the error handling are separate concerns. Handle the repository failure explicitly (show an error SnackBar) and keep the mounted check as its own line.\n- **If success and failure both show a SnackBar, both call sites need the guard** — it's easy to guard the success path and forget the `catch` branch.\n\nTwo smaller things in the surrounding code:\n\n- Prefer checking `mounted`/`context.mounted` right after the `await`, not conditionally buried deeper in branching logic, so it's obvious at the point the context is first reused.\n- If this handler lives in a `ConsumerWidget`/`StatelessWidget` (no `mounted`), use `context.mounted`, which works regardless of state-management approach; don't switch the whole widget to `StatefulWidget` just to get a `mounted` getter.\n\nSo: guard with `context.mounted` (or capture the messenger before the gap), handle the error path with the same care, and don't lean on `try`/`catch` to cover for a missing guard.",
239
+ "outputSha256": "19ecc78a6213c5d6c0806bc557bef227ac83fc4194f72e670564186c5c3b83d0",
240
+ "promptSha256": "ad3f757cb2316657fe24916f43a74a651876b46ed7e46ad01767bc3b293d424d",
241
+ "deterministic": [
242
+ true
243
+ ],
244
+ "judge": {
245
+ "verdict": "pass",
246
+ "reason": "Answer states widget may be unmounted after await (user navigated away/screen disposed), shows concrete `if (!context.mounted) return;` right after await before ScaffoldMessenger.of(context).showSnackBar, and applies it to the actual SnackBar use. Also explicitly warns try/catch is not a substitute. No fail criteria hold."
247
+ },
248
+ "passed": true
249
+ },
250
+ {
251
+ "output": "The core hazard: between `onPressed` starting the `await` and the repository call completing, the user can pop the route, the widget can be disposed, or the whole screen can be replaced. When the `await` resumes, your `BuildContext` may no longer be attached to a live element tree. Calling `ScaffoldMessenger.of(context)` or showing a SnackBar through it at that point either throws (\"Looking up a deactivated widget's ancestor is unsafe\") or attaches the SnackBar to a stale context. So:\n\n1. Check `context.mounted` immediately after the `await`, before touching `context` again.\n\n```dart\nonPressed: () async {\n final result = await repository.submit(value);\n if (!context.mounted) return;\n ScaffoldMessenger.of(context)\n .showSnackBar(SnackBar(content: Text(result.message)));\n},\n```\n\nPut the check right after the `await`, not wrapped in a broad `try`/`catch` — you want it to short-circuit before the very next context use, not to be one of several things a catch block might swallow.\n\n2. Don't silently drop the result on dispose without thinking about it. If the SnackBar's content is something the user genuinely needed to see (an error, a confirmation of a destructive action), guard-and-return means they never see it once they've navigated away. Decide deliberately whether that matters for this call: if it does, surface the outcome through a mechanism that survives the widget (the project's state/notification layer, or capture a `ScaffoldMessengerState` reference before the await if the messenger itself outlives the widget). The common \"switch to previous route and show anyway\" trick has its own pitfalls and isn't the default.\n\n3. Prefer capturing the messenger before the await when the SnackBar must survive:\n\n```dart\nfinal messenger = ScaffoldMessenger.of(context); // before await\nfinal result = await repository.submit(value);\nif (!context.mounted) return; // still guard your own state/context use\nmessenger.showSnackBar(...);\n```\n\nEven here, only use the captured `ScaffoldMessengerState` if you've confirmed it's still valid — capturing it doesn't make it immune to its own subtree being torn down.\n\n4. `ScaffoldMessenger.of(context)`, not `Scaffold.of(context).showSnackBar`. The per-scaffold API is older and doesn't survive route changes as cleanly; `ScaffoldMessenger` is the current idiom and lets the SnackBar follow the current route.\n\n5. If the handler also updates `setState` (e.g., clearing a loading flag), that call needs the same `mounted` check — the two are separate uses of the widget's liveness and both must be guarded. Guarding `context` and then calling `setState()` unconditionally is a common half-fix.\n\n6. Disable the button while the call is in flight (a `_submitting` flag) so a user can't fire overlapping requests and get two SnackBars — otherwise you can also get the \"SnackBar already queued\" pileup, which is a UX bug separate from the context-safety bug.\n\n7. If the repo call can throw, decide whether failure also produces a SnackBar (usually yes) and keep that path's context use guarded the same way. Don't let the failure branch fall through to an unguarded context use just because it's the error path.\n\nThe single rule to remember: no `context` use and no `setState()` after an `await` without a `mounted`/`context.mounted` check in between.",
252
+ "outputSha256": "1747554ca4076723bbc0b1d0d6a7b6f1d70e84854078d2c5dc0a71c341cabd06",
253
+ "promptSha256": "ad3f757cb2316657fe24916f43a74a651876b46ed7e46ad01767bc3b293d424d",
254
+ "deterministic": [
255
+ true
256
+ ],
257
+ "judge": {
258
+ "verdict": "pass",
259
+ "reason": "Answer states the widget/BuildContext may be disposed or route popped before await resumes ('the user can pop the route, the widget can be disposed... BuildContext may no longer be attached'). Shows concrete fix `if (!context.mounted) return;` right after await, applied to the actual SnackBar use via ScaffoldMessenger.of(context).showSnackBar. Explicitly warns against try/catch substitution. No fail criteria hold."
260
+ },
261
+ "passed": true
262
+ },
263
+ {
264
+ "output": "The core hazard: once you `await` inside `onPressed`, you've crossed an async gap — the widget that owns that `BuildContext` can be disposed (user navigated away, screen popped, parent rebuilt it away) before the await resolves. When it does, calling `ScaffoldMessenger.of(context)` on a stale context throws, or worse, grabs the wrong ancestor `ScaffoldMessenger`.\n\nWhat to do:\n\n1. **Check `context.mounted` immediately after the await, before touching `context`.** The `onPressed` closure holds a `BuildContext` (the `Builder`/`State`'s context), so guard with `context.mounted` (or `mounted` if you're inside a `State`):\n\n```dart\nonPressed: () async {\n final result = await repository.submit();\n if (!context.mounted) return;\n ScaffoldMessenger.of(context).showSnackBar(\n SnackBar(content: Text(result.message)),\n );\n},\n```\n\nPut the check directly after the `await` — not wrapped in a broad `try`/`catch`, which would hide the real issue and still leave the stale-context use in place.\n\n2. **Capture the `ScaffoldMessenger` *before* the await if you want the SnackBar to survive the gap.** Even with the mounted check, if the originating screen is gone the SnackBar has nowhere sensible to show. Grabbing the messenger up front and using the captured reference is the common idiom for \"show a snackbar even if this widget goes away\":\n\n```dart\nonPressed: () async {\n final messenger = ScaffoldMessenger.of(context);\n final result = await repository.submit();\n messenger.showSnackBar(SnackBar(content: Text(result.message)));\n},\n```\nBut only do this if showing the SnackBar after navigation is actually desired — otherwise the mounted check is the honest choice.\n\n3. **Don't `setState` across the gap either.** If the handler also flips a loading flag, that `setState` needs the same `mounted`/`context.mounted` guard as the SnackBar call. Guard both.\n\n4. **Guard the error path too.** If you catch an exception from the repository call and show an error SnackBar, that's *also* a context use after an await — the same mounted check applies there. Easy to guard the success branch and forget the catch branch.\n\n5. **Don't reach for `!` to silence the analyzer.** `ScaffoldMessenger.of(context)!` compiles past a real nullability gap without addressing the disposal problem at all — and `of` doesn't return nullable, so this is doubly wrong.\n\n6. **Double-tap / double-submit.** An async `onPressed` with no in-flight guard lets a user tap twice and fire two repository calls, producing two SnackBars in an unpredictable order. If the operation isn't idempotent, disable the button (or set a loading flag) for the duration.\n\nMinimal correct shape:\n\n```dart\nonPressed: () async {\n setState(() => _submitting = true);\n try {\n final result = await repository.submit();\n if (!mounted) return;\n setState(() => _submitting = false);\n ScaffoldMessenger.of(context)\n .showSnackBar(SnackBar(content: Text(result.message)));\n } catch (e) {\n if (!mounted) return;\n setState(() => _submitting = false);\n ScaffoldMessenger.of(context)\n .showSnackBar(SnackBar(content: Text('Failed: $e')));\n }\n},\n```\n\nNote `mounted` here works because the handler is inside a `State`; if it's a stateless `onPressed` with its own `context`, use `context.mounted`. Either way, the check goes right after the await, and it has to happen on every post-await path — success and failure.",
265
+ "outputSha256": "2b80ede36839d1cd9fed606990e0db3639daeef5ac5d96dbff2911c428e5770c",
266
+ "promptSha256": "ad3f757cb2316657fe24916f43a74a651876b46ed7e46ad01767bc3b293d424d",
267
+ "deterministic": [
268
+ true
269
+ ],
270
+ "judge": {
271
+ "verdict": "pass",
272
+ "reason": "Answer states the widget/BuildContext can be disposed (user navigated away, screen popped) before the await resolves. It shows the concrete fix `if (!context.mounted) return;` right after the await and before `ScaffoldMessenger.of(context).showSnackBar(...)`, applied to this prompt's SnackBar. It also warns against try/catch substitution and notes the check applies to success and error paths."
273
+ },
274
+ "passed": true
275
+ },
276
+ {
277
+ "output": "The short version: after the `await` returns, the widget that owns this button may already be gone. The `onPressed` closure holds the `BuildContext` it captured when the button was built, and if the user navigated away, popped the screen, or the widget tree otherwise rebuilt/replaced that subtree while your repository call was in flight, that context is stale. Calling `ScaffoldMessenger.of(context)` on it will throw (or worse, resolve against the wrong `Scaffold`), and if you also do anything like `setState` there, same problem.\n\nSo the pattern is: check `context.mounted` immediately after the `await`, before you touch `context` again — not wrapped in a broad try/catch, and not \"later, near the end of the handler,\" but right where the await resumes.\n\n```dart\nonPressed: () async {\n final result = await repository.submit();\n if (!context.mounted) return;\n ScaffoldMessenger.of(context).showSnackBar(\n SnackBar(content: Text(result.message)),\n );\n}\n```\n\nA few things worth flagging beyond the basic guard:\n\n**`context.mounted` vs `mounted`.** Inside a `State`'s method, prefer `mounted` (the `State`'s own flag) — it's the same guarantee and slightly clearer about what you're checking. Inside a `StatelessWidget`'s `onPressed` closure, you only have `context.mounted`. Either is fine; just don't grab a `ScaffoldMessenger` reference *before* the await and reuse it after, thinking that avoids the check — the messenger itself may be defunct too.\n\n**Grabbing the messenger early is not a substitute for the check.** Some people write `final messenger = ScaffoldMessenger.of(context);` before the await and then `messenger.showSnackBar(...)` after, on the theory that they've \"already captured it.\" That still touches scaffolding that may be gone, and it hides the lifecycle dependency. Guard after the await regardless.\n\n**Where the SnackBar actually lands.** `ScaffoldMessenger.of(context)` walks up to the nearest `ScaffoldMessenger` — which is usually provided by `MaterialApp`, so the SnackBar shows on whatever `Scaffold` is currently on screen, not necessarily the one this button was in. If this screen sits inside a nested `ScaffoldMessenger` (a tab, a dialog), expectations about which route it appears over can be wrong. If you want it pinned to *this* screen's scaffold, make sure you know which messenger you're hitting.\n\n**Double-taps.** `onPressed` fires per tap with no built-in debounce. A user tapping twice can fire the repository call twice and produce two SnackBars (or a SnackBar plus a stale-context crash if one resolves after navigation). If the call isn't idempotent, disable the button while in flight (set a loading flag, and be careful that even setting that flag via `setState` needs to respect `mounted`).\n\n**Don't let the guard swallow real errors.** `if (!context.mounted) return;` should only guard the *context use*, not the repository call. If `submit()` throws, that's a separate concern — catch and handle it before or independently of the mounted check, otherwise a genuine failure gets silently dropped when the widget also happens to be unmounted.\n\n**If this is inside a dialog or a route you're about to pop.** Showing a SnackBar on a screen you're simultaneously popping is a common footgun — the SnackBar ends up attached to a route that's animating out. Pop first, then show on the surviving context, or show before popping, but decide deliberately.\n\nThat's the whole checklist: guard context use after every await, prefer the `State`'s own `mounted` when you have it, don't pre-capture the messenger as a workaround, mind which `ScaffoldMessenger` you're hitting, debounce if the action isn't idempotent, and keep error handling separate from the mounted check.",
278
+ "outputSha256": "a5ad612104c981b2f8ca4c535e79407c0b226a2305dee0602cc33357d1d5b590",
279
+ "promptSha256": "ad3f757cb2316657fe24916f43a74a651876b46ed7e46ad01767bc3b293d424d",
280
+ "deterministic": [
281
+ true
282
+ ],
283
+ "judge": {
284
+ "verdict": "pass",
285
+ "reason": "Answer states the widget/context may be gone after await (\"navigated away, popped the screen... that context is stale\"), shows concrete fix `if (!context.mounted) return;` right after await before ScaffoldMessenger.of(context).showSnackBar, applied to this prompt's SnackBar. Explicitly rejects try/catch substitute and pre-capturing messenger. No fail criteria hold."
286
+ },
287
+ "passed": true
288
+ },
289
+ {
290
+ "output": "The core thing to watch out for: after the `await`, you're back in a callback that captured a `BuildContext`, and that context's widget may no longer be mounted — the user could have navigated away, the screen could have been rebuilt/disposed, or the button's enclosing widget could be gone entirely by the time the repository call resolves.\n\nConcretely, watch for these:\n\n**1. `context.mounted` check immediately after the `await`.** Before you call `ScaffoldMessenger.of(context)` (or anything else that uses `context`), check `context.mounted`. If you're inside a `State` method, check the `State`'s own `mounted` instead. Do this right after the `await` returns, not wrapped in a broad try/catch.\n\n```dart\nonPressed: () async {\n final result = await repo.submit();\n if (!context.mounted) return; // guard goes here, right after the await\n ScaffoldMessenger.of(context).showSnackBar(\n SnackBar(content: Text(result.message)),\n );\n},\n```\n\nWithout that guard you get the classic \"Looking up a deactivated widget's ancestor\" / \"This widget has been unmounted\" error, or worse, a SnackBar shown against a stale context that isn't the one the user is looking at anymore.\n\n**2. Capture `ScaffoldMessengerState` before the await if you want the SnackBar to survive navigation.** `ScaffoldMessenger.of(context)` looks up the *nearest* messenger above the current context. If the user has navigated to a different screen, that lookup may find a different messenger, or none. If the intent is \"show this SnackBar regardless of where the user ended up,\" grab the messenger reference *before* the await:\n\n```dart\nfinal messenger = ScaffoldMessenger.of(context); // before the await\nfinal result = await repo.submit();\nif (!context.mounted) return;\nmessenger.showSnackBar(...);\n```\n\nThe same reasoning applies if you need a `Navigator` — capture `Navigator.of(context)` before the await if you're going to navigate after it, and re-check mounted.\n\n**3. Don't swallow the error path.** If the repository call can throw, the `await` needs a `try`/`catch` (or the project's error-handling idiom), and the catch branch has the same mounted problem — the guard belongs at the top of whatever follows the await on *both* the success and failure paths, not only the success one.\n\n**4. Avoid stacking SnackBars if the button can be tapped repeatedly.** If the handler isn't disabled while the call is in flight, a user can tap several times and queue multiple SnackBars. Either disable the button while pending (a `bool _submitting` in `State`, or the state-management layer's loading flag) or call `ScaffoldMessenger.of(context).clearSnackBars()` / `hideCurrentSnackBar()` before showing the new one.\n\n**5. Match the project's state-management idiom.** If this screen already uses Riverpod/Bloc/provider, the pending/loading flag and possibly the \"show SnackBar\" side effect likely belong there rather than in local `setState` — showing a SnackBar from directly inside an `onPressed` closure is fine for a one-off, but introducing a second place where \"what state is this screen in\" lives tends to fragment things. Check how neighboring screens source data and surface results before writing this one.\n\n**6. `setState` after the await, if you use it, has the same requirement.** If you flip a loading flag back to false after the call resolves, that `setState` also needs the `mounted` check in front of it.\n\nSo the short version: guard every `context`/`setState` use after the `await` with `mounted`/`context.mounted`, capture any messenger/navigator you want to survive navigation *before* the await, handle the error path with the same guard, and give some thought to whether repeated taps or the project's existing state approach change where this logic should actually live.",
291
+ "outputSha256": "916b6eebfaa0abd60f25075e9389dbb01338e82bc749508707f3d2d88da35b0d",
292
+ "promptSha256": "ad3f757cb2316657fe24916f43a74a651876b46ed7e46ad01767bc3b293d424d",
293
+ "deterministic": [
294
+ true
295
+ ],
296
+ "judge": {
297
+ "verdict": "pass",
298
+ "reason": "Answer states the widget/context may be unmounted after await (navigated away, disposed), shows concrete fix `if (!context.mounted) return;` right after the await, and applies it to ScaffoldMessenger.of(context).showSnackBar in the actual onPressed example. No fail criteria: it explicitly warns against try/catch substitution."
299
+ },
300
+ "passed": true
301
+ },
302
+ {
303
+ "output": "The core hazard: `ScaffoldMessenger.of(context)` reads `context` **after** an `await`, and the widget that owns that `context` may already be unmounted or deactivated by the time the call resolves — the user can navigate away, pop the screen, or the widget can be rebuilt away while the repository call is still in flight. Using a stale/deactivated `context` there throws or targets a messenger that's no longer in the tree.\n\n**Guard with `context.mounted` immediately after the await, before touching `context`.**\n\n```dart\nonPressed: () async {\n final result = await repository.submit(...);\n if (!context.mounted) return; // check right after the await\n ScaffoldMessenger.of(context).showSnackBar(\n SnackBar(content: Text(result.message)),\n );\n},\n```\n\nThings to watch:\n\n- **Check right after the `await`**, not somewhere later or wrapped in a broad `try`/`catch` — the guard has to gate the next use of `context`.\n- **Inside a `State`, use `mounted`; in a callback holding a `BuildContext` (like `onPressed`), use `context.mounted`.** They mean slightly different things, so use the one that matches where the code lives.\n- **Don't reach for `!` to quiet a nullable-context warning** — that asserts past the exact gap the analyzer is flagging. Guard instead.\n- **A single awaited call is enough of an async gap.** \"It usually resolves fast\" isn't a guarantee; the gap exists regardless of timing.\n- **The repository call itself can throw.** If you're also handling errors, the catch block needs the same `context.mounted` check before it tries to show a SnackBar.\n- **Capture the messenger before the await if you want to be extra robust** — e.g. `final messenger = ScaffoldMessenger.of(context);` before the call — but you still need the `mounted` check before `showSnackBar`, since showing on an unmounted context is the problem.\n\nOne more: if you're also calling `setState()` in that handler after the await, it needs the identical `mounted` guard — the SnackBar isn't the only thing affected by the gap.",
304
+ "outputSha256": "0a8a14d7e60ed0f78dcb4569be02db8a5e38640d8696691df43baf230acf688e",
305
+ "promptSha256": "ad3f757cb2316657fe24916f43a74a651876b46ed7e46ad01767bc3b293d424d",
306
+ "deterministic": [
307
+ true
308
+ ],
309
+ "judge": {
310
+ "verdict": "pass",
311
+ "reason": "Answer states context may be unmounted/deactivated after await (user navigates away, pops screen) — criterion 1. Shows concrete fix `if (!context.mounted) return;` right after await before ScaffoldMessenger.of(context).showSnackBar — criteria 2 and 3. Explicitly warns against try/catch substitution and 'usually resolves fast' — avoids fail criteria."
312
+ },
313
+ "passed": true
314
+ },
315
+ {
316
+ "output": "The main thing: `ScaffoldMessenger.of(context)` and the SnackBar call happen *after* an `await`, so the widget that owns that `context` can already be unmounted by the time the repository future resolves — the user navigated away, the screen was popped, a rebuild replaced the subtree, etc. Using `context` at that point either throws or targets a stale/deactivated element.\n\nConcretely, what to do:\n\n- Right after the `await` (and before touching `context`), check `if (!context.mounted) return;`. Inside a `State`, use the `State`'s own `mounted` flag instead — `if (!mounted) return;`. Do the check immediately after the await, not wrapped in a broad `try`/`catch` that swallows the real reason.\n\n ```dart\n onPressed: () async {\n final result = await repository.submit(order);\n if (!context.mounted) return;\n ScaffoldMessenger.of(context).showSnackBar(\n SnackBar(content: Text('Submitted: ${result.id}')),\n );\n }\n ```\n\n- Be aware of *which* `context` you captured. If you grabbed `ScaffoldMessenger.of(context)` **before** the await into a local variable and then call `.showSnackBar` on it after, you avoid the \"no ScaffoldMessenger found\" lookup failure, but you do not avoid showing a SnackBar against a route the user has already left. Decide deliberately: if the message is genuinely tied to the current screen, guard on `mounted` and let it drop when the screen is gone; don't hoist the messenger just to force the message through.\n\n- The `await` can also throw. If the repository call fails, an unhandled exception propagates out of the async `onPressed` and surfaces via the zone/error handler rather than as a clean UI message. Catch the failure and show a distinct error SnackBar — still behind the `mounted` check.\n\n- Don't disable the button and forget to re-enable it, or re-enable it without the `mounted` check either. If you toggle a `bool _isSubmitting` around the call, the `setState` that clears it after the await needs the same guard.\n\n- Prefer `?.`/`??` over `!` when reading the result. Only assert non-null if the value's non-nullability is locally guaranteed and unaffected by the intervening await — don't use `!` to quiet an analyzer warning about the awaited value.\n\n- If a SnackBar is already showing, a second one queues behind it by default. If that's not what you want for rapid taps, consider clearing the current one (`ScaffoldMessenger.of(context).clearSnackBars()` or `hideCurrentSnackBar()`) before showing the new one — but only after the `mounted` check.\n\nThe recurring theme: every use of `context` or `setState` that sits on the far side of an `await` needs a `mounted`/`context.mounted` guard, and the awaited call itself needs its failure path handled rather than assumed to succeed.",
317
+ "outputSha256": "72914d2f4a0f957f4966c332f892951d4526a9e2cde6b8771bcaecd130f801ca",
318
+ "promptSha256": "ad3f757cb2316657fe24916f43a74a651876b46ed7e46ad01767bc3b293d424d",
319
+ "deterministic": [
320
+ true
321
+ ],
322
+ "judge": {
323
+ "verdict": "pass",
324
+ "reason": "Answer states widget/context can be unmounted after await ('user navigated away, the screen was popped'), shows concrete fix `if (!context.mounted) return;` immediately after await, and applies it to the actual SnackBar via ScaffoldMessenger.of(context) in the code sample. No fail criteria: it explicitly warns against try/catch substitution."
325
+ },
326
+ "passed": true
327
+ },
328
+ {
329
+ "output": "The core hazard: after `await repository.someCall()` returns, the widget that owns this `onPressed` may already be disposed — the user tapped the button and immediately navigated away, popped the screen, or the widget was rebuilt out of the tree. Calling `ScaffoldMessenger.of(context).showSnackBar(...)` at that point uses a stale/deactivated `BuildContext`, which throws (or targets the wrong scaffold). So:\n\n**1. Guard the context after the await.** Capture the messenger *before* the await, or check `context.mounted` immediately after it:\n\n```dart\nonPressed: () async {\n final messenger = ScaffoldMessenger.of(context); // capture before await\n final result = await repository.doThing();\n if (!context.mounted) return;\n messenger.showSnackBar(SnackBar(content: Text(result)));\n},\n```\n\nUsing `context.mounted` (Flutter 3.7+) is the idiomatic form for a `BuildContext` inside a callback. Do the check immediately after the `await`, not wrapped in a broad `try`/`catch`.\n\n**2. Don't reach for the messenger via a stale context *after* the await.** `ScaffoldMessenger.of(context)` after the await is exactly the call that can throw — capture it before, as above, or re-derive it only behind the `mounted` check.\n\n**3. Handle the error path too, not just success.** If the repository call can throw, an unhandled exception in an `async` `onPressed` becomes an unhandled async error — nothing shows. Catch it, check `mounted`, and show an error SnackBar. Don't let the success `await` and the error handling both assume the widget is still alive.\n\n**4. If the handler also calls `setState` (e.g., to clear a loading flag), that needs the same guard** — `setState` after disposal throws. Either guard with `mounted`, or capture-and-check before both the `setState` and the SnackBar.\n\n**5. Watch the button's own re-entrancy.** If the button stays tappable while the call is in flight, a double-tap fires two awaited calls and two SnackBars. Disable the button (or set a loading flag) for the duration.\n\n**6. Prefer a messenger that outlives the widget.** `ScaffoldMessenger` is designed to survive the widget that triggered a SnackBar and re-target the current `Scaffold` — but that only helps if you captured a valid messenger reference. Capturing it before the await (step 1) is what makes this work correctly across the gap.\n\n**7. Keep state-management discipline.** If this button lives in a widget that already uses the project's state-management approach (Riverpod/Bloc/Provider), the result and loading/error state arguably belong there rather than in a local `setState` + inline SnackBar — match whatever the surrounding code does instead of introducing a one-off pattern.\n\nThe one-line rule: **never touch `context`, `setState`, or a context-derived lookup after an `await` without confirming `mounted` (or having captured the reference beforehand).**",
330
+ "outputSha256": "dcfa0c4579f80ad3c34ddb73748d99b7d6b401abc4e6decba7199260b44b3f5b",
331
+ "promptSha256": "ad3f757cb2316657fe24916f43a74a651876b46ed7e46ad01767bc3b293d424d",
332
+ "deterministic": [
333
+ true
334
+ ],
335
+ "judge": {
336
+ "verdict": "pass",
337
+ "reason": "Criterion 1: states widget may be disposed/navigated away/popped before await returns. Criterion 2: shows `if (!context.mounted) return;` right after await. Criterion 3: applies it to the SnackBar via messenger.showSnackBar after the check. No fail criteria: it explicitly warns against try/catch substitution and stale context use."
338
+ },
339
+ "passed": true
340
+ }
341
+ ]
342
+ },
343
+ {
344
+ "id": "controller-disposal",
345
+ "kind": "behavior",
346
+ "prompt": "I'm adding a TextEditingController and an AnimationController to this Flutter form's State, both created in initState. What do I need to do to keep this correct?",
347
+ "strictness": "high",
348
+ "trials": 10,
349
+ "passes": 10,
350
+ "passRate": 1,
351
+ "passAtK": 1,
352
+ "grader": "judge",
353
+ "status": "ran",
354
+ "trialRecords": [
355
+ {
356
+ "output": "Both need to be released in `dispose()`, and their lifecycle should be symmetric with `initState()`:\n\n```dart\nclass _MyFormState extends State<MyForm> {\n late final TextEditingController _nameController;\n late final AnimationController _fadeController;\n\n @override\n void initState() {\n super.initState();\n _nameController = TextEditingController();\n _fadeController = AnimationController(\n vsync: this,\n duration: const Duration(milliseconds: 300),\n );\n }\n\n @override\n void dispose() {\n _nameController.dispose();\n _fadeController.dispose();\n super.dispose();\n }\n ...\n}\n```\n\nKey points to keep this correct:\n\n1. **Dispose both, in `dispose()`, before calling `super.dispose()`.** Each holds underlying platform resources that live for the app's lifetime if not released — not just the screen's.\n\n2. **`AnimationController` requires a `TickerProvider`.** Your `State` must use `SingleTickerProviderStateMixin` (or `TickerProviderStateMixin` if more than one controller). Without it, `vsync: this` won't compile.\n\n3. **Use `late final` if you assign in `initState`** rather than declaring nullable — it keeps the fields non-null everywhere else and avoids `!` at every use site.\n\n4. **Don't create controllers in `build()`.** They'd be recreated on every rebuild and leak the previous ones. Create them once in `initState()`.\n\n5. **Listeners added to the controllers** (e.g. `_nameController.addListener(...)`) don't need explicit removal if the listener is on a controller you're disposing — disposing the controller tears down its listeners. But if you attach a listener to something you *don't* dispose (a shared object), remove it in `dispose()`.\n\n6. **If you use the controller across an `await`** (e.g. reading `_nameController.text` after a network call), that's fine — the controller outlives the await as long as the widget is still mounted. But guard your `setState`/`context` uses with `mounted`/`context.mounted` after any `await`.\n\n7. **If this form's state actually belongs in the project's state layer** (Riverpod/Bloc/Provider), consider whether `TextEditingController` should live in the `State` while the *form values* live in the controller/notifier — the `TextEditingController` is a UI concern and legitimately belongs in `State`, so mixing is fine here, but don't duplicate the sources of truth.\n\nRun `flutter analyze` after; an undisposed controller won't be flagged by the analyzer, so treat the `dispose()` symmetry as something you verify by reading, not by tooling.",
357
+ "outputSha256": "b7ee41bf08c8858f60ee7a0ddd48c6333b6c36b8f3097a1e832c7392f2cf2405",
358
+ "promptSha256": "7d0342c6d00b11af2166eb0f9074f13c4cde42ac49fab3a6a490d4ade78f9564",
359
+ "deterministic": [],
360
+ "judge": {
361
+ "verdict": "pass",
362
+ "reason": "Answer disposes both controllers: code shows `_nameController.dispose(); _fadeController.dispose();` inside dispose(), and point 1 states 'Dispose both, in dispose(), before calling super.dispose()'. super.dispose() is called last in the code and explicitly stated. All three pass criteria concretely met."
363
+ },
364
+ "passed": true
365
+ },
366
+ {
367
+ "output": "Both controllers need symmetric lifecycle handling — created in `initState`, released in `dispose`. Here's the complete picture:\n\n**1. Create both in `initState()`, not in the field initializer**\n\n```dart\nclass _MyFormState extends State<MyForm> {\n late final TextEditingController _textController;\n late final AnimationController _animationController;\n\n @override\n void initState() {\n super.initState(); // always call super first\n _textController = TextEditingController(text: widget.initialText);\n _animationController = AnimationController(\n vsync: this,\n duration: const Duration(milliseconds: 250),\n );\n }\n```\n\nIf a controller needs constructor arguments that depend on `widget.*`, you must use `initState` (or `late` with a getter) — you can't read `widget` during field initialization. `AnimationController` also needs `vsync: this`, which requires `TickerProviderStateMixin` (see below), so `initState` is the right place regardless.\n\n**2. Add the ticker provider mixin for `AnimationController`**\n\n```dart\nclass _MyFormState extends State<MyForm>\n with SingleTickerProviderStateMixin { // use TickerProviderStateMixin if you have >1 animation\n```\n\nWithout one of these mixins, `vsync: this` won't compile.\n\n**3. Dispose both in `dispose()`, and call `super.dispose()` last**\n\n```dart\n @override\n void dispose() {\n _textController.dispose();\n _animationController.dispose();\n super.dispose(); // last\n }\n```\n\nThe ordering matters: dispose your controllers first, then `super.dispose()`. Disposing the controllers keeps their underlying platform/engine resources alive otherwise, for the app's lifetime — not just the screen's. Never touch `context` inside `dispose()`.\n\n**4. If you use the text controller's value to drive UI, rebuild or listen deliberately**\n\n- If the UI depends on the text content, either pass the controller to a `TextFormField`/`TextField` (which listens internally) or add a listener that calls `setState` — and **remove that listener in `dispose()` too**:\n\n```dart\n_textController.addListener(_onTextChanged);\n// ...\nvoid _onTextChanged() => setState(() {});\n// in dispose, before disposing the controller:\n_textController.removeListener(_onTextChanged);\n```\n\n**5. Guard anything async**\n\nIf either controller is used or `setState()` is called after an `await` (e.g. a save/submit call), check `mounted` first:\n\n```dart\nFuture<void> _submit() async {\n await _repository.save(_textController.text);\n if (!mounted) return; // widget disposed while awaiting\n setState(() { ... });\n _animationController.forward();\n}\n```\n\nNever call `setState` or touch `context` across an async gap without that `mounted` check.\n\n**6. Match the project's conventions**\n\nBefore writing this, check whether this State should exist at all — if the form's state belongs in the app's existing state-management layer (Riverpod/Bloc/Provider), putting controllers in a `StatefulWidget` may be the wrong home. But note: **controllers are still typically owned by a `StatefulWidget` even in those projects**, since they're tied to the widget's lifecycle via the ticker/`dispose` machinery. Match whatever the neighboring forms in the codebase already do.\n\n---\n\n**Summary of the invariants you must hit:**\n- Both created in `initState` (after `super.initState()`).\n- `TickerProvider` mixin present for the `AnimationController`.\n- Both disposed in `dispose()`, before `super.dispose()`.\n- Any listener you added is removed before disposal.\n- Every `await`-then-`context`/`setState` path is guarded by `mounted`.\n\nVerify with `flutter analyze` and `dart format --set-exit-if-changed .` both exiting clean, plus `flutter test` if any tests touch this screen.",
368
+ "outputSha256": "3c693ad46ce3a012277b851bc4c23cabec0d9537eceda7241878113e11637c94",
369
+ "promptSha256": "7d0342c6d00b11af2166eb0f9074f13c4cde42ac49fab3a6a490d4ade78f9564",
370
+ "deterministic": [],
371
+ "judge": {
372
+ "verdict": "pass",
373
+ "reason": "Answer states both controllers need disposal (step 3: '_textController.dispose(); _animationController.dispose();'), shows concrete .dispose() calls in State.dispose(), and explicitly calls super.dispose() last with comment '// last' and 'dispose your controllers first, then super.dispose()'. Also notes resources leak for app lifetime."
374
+ },
375
+ "passed": true
376
+ },
377
+ {
378
+ "output": "Create both in `initState` and release both in `dispose`, in the mirror order — every controller a `State` acquires has to be disposed when that `State` goes away, regardless of how short-lived the screen feels.\n\nConcretely:\n\n```dart\nclass _MyFormState extends State<MyForm> {\n late final TextEditingController _controller;\n late final AnimationController _animationController;\n\n @override\n void initState() {\n super.initState();\n _controller = TextEditingController();\n _animationController = AnimationController(\n vsync: this,\n duration: const Duration(milliseconds: 200),\n );\n }\n\n @override\n void dispose() {\n _animationController.dispose();\n _controller.dispose();\n super.dispose();\n }\n ...\n}\n```\n\nPoints that keep this correct:\n\n- **Call `super.initState()` first, `super.dispose()` last.** A common bug is forgetting the `super.dispose()` call or putting `dispose()` before releasing the controllers.\n- **`AnimationController` needs a `TickerProvider`.** Since you have exactly one controller, use `SingleTickerProviderStateMixin` on the `State` (or `TickerProviderStateMixin` if a second `AnimationController` ever shows up). Without the mixin, `vsync: this` won't type-check.\n- **Use `late final` for the field types.** It lets you assign in `initState` while keeping the fields non-nullable, so you don't need `!` at every use site. Don't rely on `late` to defer creation to a later method; create in `initState`.\n- **Don't recreate controllers in `build()`.** Constructing a `TextEditingController` or `AnimationController` inside `build` leaks a new instance on every rebuild. `build` reads them; `initState` creates them.\n- **`TextEditingController` also needs a `dispose()` even if you never read `.text`.** It holds a listener list and platform resources; leaking it survives past the screen's life.\n- **If you pass `_controller` down to a `TextField`,** dispose it here in this `State`, not in the child — the owner disposes.\n- **If you add an `AnimationController` listener or a `StreamSubscription`,** cancel/remove it in `dispose` too, not just call `.dispose()` on the controller.\n\nFor this repo specifically: `flutter analyze` will catch a missing `dispose` override if the analyzer's lints are on, but it won't catch `super.dispose()` ordering. Run `flutter analyze` and `dart format --set-exit-if-changed .` before calling it done, per the skill's verification step.\n\nOne adjacent thing worth wiring now: if this form does anything async on submit (validate → call a repository), any `setState()` or `context` use after that `await` needs `mounted`/`context.mounted` checked first — that's the other half of \"correct lifecycle handling\" and is easy to miss when the controllers are the visible part.",
379
+ "outputSha256": "a7b63371ed3c220e3c1ccb859faf4fc07ea4a432a176eed8610f4c9e58352897",
380
+ "promptSha256": "7d0342c6d00b11af2166eb0f9074f13c4cde42ac49fab3a6a490d4ade78f9564",
381
+ "deterministic": [],
382
+ "judge": {
383
+ "verdict": "pass",
384
+ "reason": "Answer disposes both controllers: code shows `_animationController.dispose(); _controller.dispose();` in dispose(), and text says 'release both in dispose'. It explicitly states 'Call super.initState() first, super.dispose() last' and warns against putting dispose() before releasing controllers. All three pass criteria concretely met."
385
+ },
386
+ "passed": true
387
+ },
388
+ {
389
+ "output": "Create both controllers in `initState()` and dispose both in `dispose()`, in creation order's reverse if you like but symmetric either way:\n\n```dart\nlate final TextEditingController _emailController;\nlate final AnimationController _shakeController;\n\n@override\nvoid initState() {\n super.initState();\n _emailController = TextEditingController();\n _shakeController = AnimationController(\n vsync: this,\n duration: const Duration(milliseconds: 300),\n );\n}\n\n@override\nvoid dispose() {\n _emailController.dispose();\n _shakeController.dispose();\n super.dispose();\n}\n```\n\nThings that go wrong here and how to avoid them:\n\n- **Calling `super.dispose()` first.** Always dispose your own resources before calling `super.dispose()`; the framework tears down the element at that point and any later use of your controllers is undefined.\n- **Forgetting the `TickerProvider`.** `AnimationController` needs a `vsync`; `SingleTickerProviderStateMixin` (one controller) or `TickerProviderStateMixin` (two or more) on the `State`. With the two controllers above, use `TickerProviderStateMixin` — `SingleTickerProviderStateMixin` will assert at runtime.\n- **`late final` versus nullable.** If you always assign in `initState`, `late final` avoids a pointless `?` at every use site and gives you a real error if you forget to initialize. Don't use `late` for a controller you sometimes don't create — that just moves the failure to first access.\n- **Using a controller after an `await` without checking `mounted`.** If any method on this `State` awaits (a form submit, a validation call) and then touches `_shakeController` or calls `setState`, guard it:\n ```dart\n Future<void> _submit() async {\n await _repository.save(...);\n if (!mounted) return;\n _shakeController.forward(from: 0);\n }\n ```\n Same for `_emailController` if you read `.text` after an await.\n- **Adding a listener without removing it.** If you call `_emailController.addListener(...)` or `_shakeController.addStatusListener(...)`, remove it in `dispose()` as well — or, better, drive the rebuild through `AnimatedBuilder`/`ListenableBuilder` instead of a manual listener + `setState`, which eliminates the cleanup entirely.\n- **Disposing while an animation is running.** `AnimationController.dispose()` while ticking is fine (the framework handles it), but any `addStatusListener` callback that fires during teardown must not touch `context`, `setState`, or the other controller.\n- **Recreating instead of reusing.** Don't call `setState` to rebuild the controllers themselves; create in `initState`, mutate via `.text`/`.forward()`/`.reverse()` thereafter. If a controller genuinely must be replaced (rare), dispose the old one in the same synchronous block.\n\nThen verify: `flutter analyze`, `dart format --set-exit-if-changed .`, and `flutter test` all exit clean, and grep the file for `dispose()` to confirm every `initState` acquisition has a matching release.",
390
+ "outputSha256": "7bf53544982cd93eff853d58405319ed81dd4d400bf179b86b0c20d721d93377",
391
+ "promptSha256": "7d0342c6d00b11af2166eb0f9074f13c4cde42ac49fab3a6a490d4ade78f9564",
392
+ "deterministic": [],
393
+ "judge": {
394
+ "verdict": "pass",
395
+ "reason": "Answer disposes both controllers: `_emailController.dispose(); _shakeController.dispose();` inside dispose() (criterion 1 & 2), and calls `super.dispose()` last after them, explicitly warning against calling it first (criterion 3)."
396
+ },
397
+ "passed": true
398
+ },
399
+ {
400
+ "output": "Create both in `initState()`, cancel/dispose both in `dispose()`, and cancel the animation controller's outstanding work explicitly if anything is waiting on it.\n\nConcretely, for each controller:\n\n**TextEditingController**\n- Create it in `initState()` (not as a field initializer if you need to read a widget's initial value — `widget` isn't available at field-initializer time, only in `initState`).\n- Depending on your form, either let it own the field text or back it with a `text` listener / a `name` paired with the field; whichever you choose, keep the controller as the single source of truth rather than also mirroring the text into a separate string field you mutate independently.\n- Call `.dispose()` in `dispose()`. Optionally remove any listener you added before disposing, though disposing the controller makes that moot if the listener was only on this controller.\n- If you add a listener (e.g. to enable a submit button on change), add it in `initState` and remove it in `dispose` if there's any chance the controller outlives the state or the listener closes over `context`/`setState`.\n\n**AnimationController**\n- It needs a `TickerProvider`. If your `State` has one controller, mix in `SingleTickerProviderStateMixin`; if it has more than one `AnimationController` (or any other ticker source), use `TickerProviderStateMixin`. Using `SingleTickerProviderStateMixin` with two animation controllers throws at runtime.\n- Create it in `initState()` with `vsync: this`.\n- Call `.dispose()` in `dispose()`. For an `AnimationController` this also stops it; you don't need a separate `.stop()`.\n- If the animation is started from an async flow, guard against calling `.forward()`/`.reverse()` after the `State` is disposed — check `mounted` (or that the controller hasn't been disposed) before starting it in a callback that can outlive the widget.\n\n**Both together — the ordering and the async-gap rules**\n- `dispose()` order: dispose your own controllers before calling `super.dispose()`, and if one controller drives the other (e.g. an animation completing triggers a `setState` that touches the text controller), dispose the one whose listeners you've removed first — in practice, remove listeners, then dispose controllers, then `super.dispose()`.\n- Any `await` that is followed by a use of `context` or `setState()` (which touches these controllers) needs a `mounted` / `context.mounted` check immediately after the `await`. Fast-resolving futures are not a guarantee that the widget is still mounted when they complete.\n- Don't use `!` to silence a \"possibly null\" analyzer complaint around these controllers; structure the code so nullability is actually guaranteed (e.g. create in `initState`, so by the time `build` runs they're non-null).\n\nA minimal shape:\n\n```dart\nclass _MyFormState extends State<MyForm>\n with SingleTickerProviderStateMixin {\n late final TextEditingController _textController;\n late final AnimationController _animController;\n\n @override\n void initState() {\n super.initState();\n _textController = TextEditingController(text: widget.initialText);\n _animController = AnimationController(\n vsync: this,\n duration: const Duration(milliseconds: 300),\n );\n }\n\n @override\n void dispose() {\n _animController.dispose();\n _textController.dispose();\n super.dispose();\n }\n\n Future<void> _submit() async {\n await _repository.save(_textController.text);\n if (!mounted) return;\n setState(() { /* ... */ });\n _animController.forward();\n }\n}\n```\n\nAnd a self-check before you call it done: `flutter analyze` and `dart format` clean, `flutter test` passing, no `setState`/context use after an `await` without a `mounted` check, and every controller created in `initState` released in `dispose`.",
401
+ "outputSha256": "10d997140b020d472072c5055ddc0af85fae79f0bcf926b791f29e9d6f70b112",
402
+ "promptSha256": "7d0342c6d00b11af2166eb0f9074f13c4cde42ac49fab3a6a490d4ade78f9564",
403
+ "deterministic": [],
404
+ "judge": {
405
+ "verdict": "pass",
406
+ "reason": "Answer states both controllers must be disposed (TextEditingController and AnimationController sections), shows concrete `.dispose()` calls in the State's dispose(), and the code sample orders `_animController.dispose(); _textController.dispose(); super.dispose();` with super last, explicitly stating 'dispose your own controllers before calling super.dispose()'."
407
+ },
408
+ "passed": true
409
+ },
410
+ {
411
+ "output": "Create both controllers in `initState()`, and release both in `dispose()` — symmetrically. Concretely:\n\n**In `initState()`:**\n- Create the `TextEditingController` (e.g. `_controller = TextEditingController()`), ideally seeding it from an initial value passed via `widget` if the form needs one.\n- Create the `AnimationController` with its `vsync: this` (which requires the `State` to mix in `SingleTickerProviderStateMixin`, or `TickerProviderStateMixin` if more than one controller will animate).\n- Add any listeners the form needs (e.g. `_controller.addListener(...)` for validation-as-you-type, or an `AnimationController` status listener) *after* creation, and remember those listeners die with the controller.\n- Call `super.initState()` — put it first, then your setup, so nothing you do depends on a half-initialized superclass.\n\n**In `dispose()`:**\n- Dispose both controllers: `_controller.dispose()` and `_animationController.dispose()`.\n- If you added a listener that is not owned by the controller (e.g. a listener on a `Stream` or a `ChangeNotifier` from elsewhere), remove it here explicitly — disposing the controller does not remove listeners you attached to a *different* object.\n- Dispose in reverse order of creation is a tidy convention but not required; what matters is that both are disposed.\n- Call `super.dispose()` **last**, after your own cleanup.\n\n**The sharp edges specific to these two:**\n\n1. **Don't create controllers in `build()`.** A `build()` can run arbitrarily often; a controller created there leaks on every rebuild and resets its state. `initState()` is the only correct place for form-lifetime controllers.\n\n2. **Async gaps still apply.** If either controller is used in a callback that runs after an `await` — e.g. you `await` validation or a submit, then call `_animationController.forward()` or `_controller.text` — check `mounted` first. After the `await`, the widget may have been disposed and the controller already disposed; touching a disposed controller is an error.\n\n3. **A disposed controller is a landmine, not a no-op.** Calling `forward()`/`text` on an `AnimationController`/`TextEditingController` after disposal throws. This is exactly why the `mounted` check before post-`await` use matters, and why you must not dispose more than once.\n\n4. **`AnimationController` needs a ticker provider.** Make sure the `State`'s mixin (`SingleTickerProviderStateMixin`/`TickerProviderStateMixin`) is declared, and that you don't also try to dispose the ticker yourself — the controller owns it.\n\n5. **If the animation loops or repeats**, `dispose()` still stops it; you don't need to call `stop()`/`reset()` first, though doing so before disposal is harmless and sometimes clearer in intent.\n\n**Minimal shape:**\n\n```dart\nclass _MyFormState extends State<MyForm>\n with SingleTickerProviderStateMixin {\n late final TextEditingController _controller;\n late final AnimationController _animationController;\n\n @override\n void initState() {\n super.initState();\n _controller = TextEditingController(text: widget.initialValue);\n _animationController = AnimationController(\n vsync: this,\n duration: const Duration(milliseconds: 250),\n );\n _controller.addListener(_validate);\n }\n\n @override\n void dispose() {\n _controller.removeListener(_validate); // listener on a controller you own — explicit remove is clear, dispose would drop it anyway\n _controller.dispose();\n _animationController.dispose();\n super.dispose();\n }\n // ...\n}\n```\n\nOne judgment call to make deliberately: the `removeListener` above is technically redundant since disposing the controller drops its listeners, but keeping it makes the symmetry explicit. Remove it if the project's style treats it as noise. What you must **not** do is rely on `dispose()` of one object to clean up a listener you attached to a *different* one — that assumption is the actual bug source.\n\nFinally, when the form also does async work (submit, validation against a repository), apply the same discipline as any other async callback: `if (!mounted) return;` immediately after the `await`, before touching `setState`, `context`, or either controller.",
412
+ "outputSha256": "268488aec136ad4246cb5aa6fa71ecde86451a0591b7218223254130efe70eba",
413
+ "promptSha256": "7d0342c6d00b11af2166eb0f9074f13c4cde42ac49fab3a6a490d4ade78f9564",
414
+ "deterministic": [],
415
+ "judge": {
416
+ "verdict": "pass",
417
+ "reason": "Answer states both controllers must be disposed: '_controller.dispose() and _animationController.dispose()' in dispose(), shown in code. It explicitly says 'Call super.dispose() last, after your own cleanup', and the code places super.dispose() after both controller disposals. All three pass criteria concretely satisfied."
418
+ },
419
+ "passed": true
420
+ },
421
+ {
422
+ "output": "Create both controllers in `initState()`, and dispose both in `dispose()` — in the reverse order from creation, before calling `super.dispose()`. That symmetry is the core requirement.\n\n```dart\nclass _OrderFormState extends State<OrderForm>\n with SingleTickerProviderStateMixin {\n late final TextEditingController _notesController;\n late final AnimationController _submitController;\n\n @override\n void initState() {\n super.initState();\n _notesController = TextEditingController(text: widget.initialNotes);\n _submitController = AnimationController(\n vsync: this,\n duration: const Duration(milliseconds: 300),\n );\n }\n\n @override\n void dispose() {\n _submitController.dispose();\n _notesController.dispose();\n super.dispose();\n }\n\n @override\n Widget build(BuildContext context) {\n return TextField(controller: _notesController);\n }\n}\n```\n\nA few things that trip people up here:\n\n**The `AnimationController` needs a `vsync`.** That means the `State` must mix in a ticker provider — `SingleTickerProviderStateMixin` for one controller, `TickerProviderStateMixin` if you add more later. Without it the controller won't compile. The `vsync: this` reference also means the controller must not outlive the `State` that provides the ticker, which is another reason disposal is non-optional.\n\n**Dispose order.** Dispose the `AnimationController` before the `TextEditingController` is not strictly required, but call `super.dispose()` last — always. Releasing your controllers after `super.dispose()` can touch a defunct element.\n\n**Never recreate the controllers in `build()`.** They're `late final` and assigned once in `initState()`. If they're created inline in `build`, every rebuild spawns a fresh controller and leaks the previous one. If the widget needs to react to a changed `widget.initialNotes`, use `didUpdateWidget` and assign to the existing controller's `text` — don't create a new one.\n\n**If you start a ticker loop, stop it.** A repeating animation that runs indefinitely should be `.stop()`ed or `.dispose()`d in `dispose()`; a controller left running keeps requesting frames and holds the ticker alive.\n\n**TextEditingController listeners.** If you add a listener for validation or live formatting, it goes in `initState()` alongside the controller and comes off (`removeListener`) in `dispose()` — disposing the controller drops its listeners, but being explicit is cheap and avoids surprises if the listener closes over something you also dispose.\n\n**The async-gap rule still applies around these controllers.** If any `await` is followed by reading `_notesController.text`, calling `_submitController.forward()`, or touching `setState`/`context`, check `mounted` first — navigation away mid-submit can otherwise fire into a disposed `State`.\n\nSince you're touching a form, also confirm which state-management approach the project already uses before wiring in loading/error/empty handling for the submit path, so the controllers live in the `State` as view-local concerns while the actual submission state (pending/success/failure) goes wherever the project keeps it.",
423
+ "outputSha256": "b199cc36bca7f2f7d380a66aa3df1eb4371caa9af085390fd2a30ea8a70cef11",
424
+ "promptSha256": "7d0342c6d00b11af2166eb0f9074f13c4cde42ac49fab3a6a490d4ade78f9564",
425
+ "deterministic": [],
426
+ "judge": {
427
+ "verdict": "pass",
428
+ "reason": "Answer disposes both controllers: `_submitController.dispose(); _notesController.dispose();` inside dispose() (criterion 1 & 2), and calls `super.dispose()` last, explicitly stating 'call super.dispose() last — always' (criterion 3). No fail criteria hold."
429
+ },
430
+ "passed": true
431
+ },
432
+ {
433
+ "output": "For both controllers created in `initState`, the correctness requirements are:\n\n**1. Dispose both in `dispose()`**\n\nEvery controller created in `initState()` must be released in `dispose()`, symmetric with its creation:\n\n```dart\nclass _MyFormState extends State<MyForm> {\n late final TextEditingController _textController;\n late final AnimationController _animationController;\n\n @override\n void initState() {\n super.initState();\n _textController = TextEditingController();\n _animationController = AnimationController(\n vsync: this,\n duration: const Duration(milliseconds: 300),\n );\n }\n\n @override\n void dispose() {\n _textController.dispose();\n _animationController.dispose();\n super.dispose();\n }\n ...\n}\n```\n\nOrder matters: dispose your controllers **before** calling `super.dispose()`. The `super.dispose()` call is always last.\n\n**2. `AnimationController` needs a `vsync`**\n\n`AnimationController` requires a `vsync` argument. The standard way is to add `SingleTickerProviderStateMixin` (for one controller) or `TickerProviderStateMixin` (for two or more) to your `State` class:\n\n```dart\nclass _MyFormState extends State<MyForm> with SingleTickerProviderStateMixin {\n```\n\nIf you ever add a second `AnimationController`, switch to `TickerProviderStateMixin` — `SingleTickerProviderStateMixin` will assert at runtime otherwise.\n\n**3. Don't dispose while the animation is still driving something**\n\nIf an animation listener or status listener reconstructs widgets, remove those listeners before disposing, or ensure any pending callback can't run after disposal. A listener firing after `dispose()` and touching `setState`/`context` throws. If you add listeners, add their removal in `dispose()` too, above the controller disposals.\n\n**4. Guard any `setState`/`context` use after an `await`**\n\nIf either controller is driven by an async callback (e.g., `_textController.text` read after an awaited repository or validation call), check `mounted` immediately after the `await` before the next `setState()` or `context` use:\n\n```dart\nFuture<void> _submit() async {\n final value = _textController.text;\n await widget.repository.save(value);\n if (!mounted) return;\n setState(() { /* ... */ });\n}\n```\n\n**5. Verify**\n\nRun `flutter analyze`, `dart format --set-exit-if-changed .`, and `flutter test` — all should exit clean. The analyzer will flag a `late final` field that's read before assignment, an undisposed-controller pattern it can detect, and any missing `vsync`.\n\nThe short version: create in `initState`, release both in `dispose` (before `super.dispose()`), add the appropriate ticker mixin, and guard any post-`await` use.",
434
+ "outputSha256": "9ac2a0b3ed7625121259823ab886c2d055aeb088bb2a6c55325aad43e907d836",
435
+ "promptSha256": "7d0342c6d00b11af2166eb0f9074f13c4cde42ac49fab3a6a490d4ade78f9564",
436
+ "deterministic": [],
437
+ "judge": {
438
+ "verdict": "pass",
439
+ "reason": "Answer disposes both controllers: `_textController.dispose(); _animationController.dispose();` inside dispose() (criterion 1 & 2). It explicitly states ordering: 'dispose your controllers before calling super.dispose(). The super.dispose() call is always last' (criterion 3). No fail criteria hold."
440
+ },
441
+ "passed": true
442
+ },
443
+ {
444
+ "output": "You're creating two controllers in `initState`, so the correctness work is almost entirely in `dispose()`, plus one null-safety/wiring detail per controller. Concretely:\n\n**Dispose them symmetrically in `dispose()`**\n\nEvery controller created in `initState()` must be released in `dispose()`, in the same order they were created (or any order, but be consistent). If you skip this, the controller keeps its underlying platform resources/listeners alive past the widget's lifetime — it's not freed just because the screen was popped.\n\n```dart\nlate final TextEditingController _emailController;\nlate final AnimationController _fadeController;\n\n@override\nvoid initState() {\n super.initState();\n _emailController = TextEditingController();\n _fadeController = AnimationController(\n vsync: this, // requires SingleTickerProviderStateMixin\n duration: const Duration(milliseconds: 300),\n );\n}\n\n@override\nvoid dispose() {\n _emailController.dispose();\n _fadeController.dispose(); // stops the ticker, releases it\n super.dispose(); // call super LAST\n}\n```\n\nKey points on `dispose()`:\n- Call `super.dispose()` **last**, after your own controllers.\n- `AnimationController.dispose()` stops and releases the ticker, so you don't need a separate `.stop()` call.\n- The `TextEditingController` should also be disposed if you ever listen to it via `addListener` — dispose removes those.\n\n**Declare them as `late final`, and don't recreate them**\n\nUse `late final TextEditingController _emailController;` (assigned once in `initState`). Never reassign them in `build()` — a new controller on every build leaks the old one and silently breaks the text field / animation.\n\n**The AnimationController needs a ticker provider**\n\n`vsync: this` requires the `State` to mix in `SingleTickerProviderStateMixin` (or `TickerProviderStateMixin` if you have more than one `AnimationController`). Without it, this won't compile:\n\n```dart\nclass _MyFormState extends State<MyForm>\n with SingleTickerProviderStateMixin {\n```\n\nSince you're adding a second controller in the future, be ready to move to `TickerProviderStateMixin` — but `SingleTickerProviderStateMixin` is correct for exactly one `AnimationController`.\n\n**Null-safety / wiring details**\n\n- `TextEditingController` — pass it to `TextFormField(controller: _emailController)` and read via `_emailController.text`. Don't store the raw text in a separate `String` field and try to keep them in sync; read the controller instead.\n- If you wire up a `Form` with a `GlobalKey<FormState>`, that key is `final` and created inline (`final _formKey = GlobalKey<FormState>();`), not in `initState` — it doesn't need disposal.\n- If you add a validator that depends on async data, remember the `mounted`/`context.mounted` check after any `await` before touching `setState` or `context`.\n\n**If the Form can be disposed while its work is still in flight**\n\nAny `await` that follows with a `setState()` or `context` use needs a `mounted` check immediately after the await, and the same discipline applies to any listener callbacks that could fire after disposal — shut down the listener in `dispose()`.\n\nSo the short checklist: create both in `initState` (with `late final`), add the `SingleTickerProviderStateMixin` for the animation, and dispose both in `dispose()` before `super.dispose()`.",
445
+ "outputSha256": "83f6c28d62e4f3edceb571c2bfd3325104ed78c2d71325b7320651a78f7af397",
446
+ "promptSha256": "7d0342c6d00b11af2166eb0f9074f13c4cde42ac49fab3a6a490d4ade78f9564",
447
+ "deterministic": [],
448
+ "judge": {
449
+ "verdict": "pass",
450
+ "reason": "Answer disposes both controllers: `_emailController.dispose(); _fadeController.dispose();` in dispose() (criterion 1 & 2), and explicitly comments `super.dispose(); // call super LAST` with bullet 'Call super.dispose() last, after your own controllers' (criterion 3). Also explains platform resources leak past widget lifetime."
451
+ },
452
+ "passed": true
453
+ },
454
+ {
455
+ "output": "Both controllers are `initState`-acquired disposable resources, so the core requirement is symmetry: whatever `initState()` acquires, `dispose()` must release.\n\n**Create in `initState`:**\n\n```dart\nlate final TextEditingController _emailController;\nlate final AnimationController _shakeController;\n\n@override\nvoid initState() {\n super.initState();\n _emailController = TextEditingController();\n _shakeController = AnimationController(\n vsync: this,\n duration: const Duration(milliseconds: 300),\n );\n}\n```\n\nUse `late final` (not nullable) since both are guaranteed assigned by the end of `initState`. For `AnimationController`, the enclosing `State` needs a `TickerProvider` — `SingleTickerProviderStateMixin` for one controller, `TickerProviderStateMixin` for more than one.\n\n**Dispose in `dispose()`, and call `super.dispose()` last:**\n\n```dart\n@override\nvoid dispose() {\n _emailController.dispose();\n _shakeController.dispose();\n super.dispose();\n}\n```\n\nOrder matters: release your own resources first, then `super.dispose()`.\n\n**Why it matters beyond tidy code.** Both controllers hold platform-side resources (a `TextEditingController` backs a real editable-text platform view; an `AnimationController` registers a ticker). They are not garbage-collected the moment the `State` is dropped — an undisposed controller keeps those resources alive for the app's lifetime, and an undisposed `AnimationController` keeps ticking, leaking a frame callback that can also throw once its `State` is gone. \"It's a short-lived screen\" is not a reason to skip disposal; the leak outlives the screen.\n\nA few adjacent points that come up with this exact combination:\n\n- **Don't read `_emailController.text` after an `await` without checking `mounted`.** If you validate/format/submit asynchronously and then call `setState()` or touch `context`, guard it: `if (!mounted) return;` immediately after the `await`. Disposal doesn't save you here — the widget can be deactivated while the future is pending, and `setState` on an unmounted `State` throws.\n- **Don't recreate or reassign the controllers outside `initState`.** With `late final` the analyzer will catch an accidental reassignment, which is exactly what you want.\n- **Prefer listening to the controller (`_emailController.addListener` or `ValueListenableBuilder`) over calling `setState` on every keystroke** if you only need to react to text changes — and if you do add a manual listener, remove it in `dispose()` too, since `dispose()` on the controller does not remove listeners you attached to it.\n- **If the `AnimationController` is driven by a `Future`/async flow** (e.g. trigger a shake after an async validation failure), that trigger is another async-gap site: check `mounted` before calling `.forward()`.\n\nNet: `late final` fields, `SingleTickerProviderStateMixin` (or `TickerProviderStateMixin` if you add more animations later), both disposed in `dispose()` before `super.dispose()`, and `mounted` guards on any post-`await` use of `setState`/`context`. Verify with `flutter analyze` (catches missing `dispose` overrides on `State` with disposable fields, unused `late final`, and non-null-safety slips) and `dart format --set-exit-if-changed .`.",
456
+ "outputSha256": "649465819e003f789498a0be6f5211b35216f32b5c3c7b521b1d5f5ca95e5a79",
457
+ "promptSha256": "7d0342c6d00b11af2166eb0f9074f13c4cde42ac49fab3a6a490d4ade78f9564",
458
+ "deterministic": [],
459
+ "judge": {
460
+ "verdict": "pass",
461
+ "reason": "Answer disposes both controllers: `_emailController.dispose(); _shakeController.dispose();` inside dispose(), then `super.dispose();` last, explicitly stating 'release your own resources first, then super.dispose()'. It also explains the platform-resource leak outliving the screen, satisfying all three pass criteria and no fail criteria."
462
+ },
463
+ "passed": true
464
+ }
465
+ ]
466
+ }
467
+ ],
468
+ "verdict": "fail",
469
+ "scope": "bundled",
470
+ "skillDigest": "102b44d8a12cb118dbdd4d341abdb4db5d94afbbd7b6b4cc5e88405457d73c39",
471
+ "catalogDigest": "97f9af01aafac82ae21a63c6af2a2f24fcfe067dc32a7cfdcde9a69a91fa9aae",
472
+ "judgePromptVersion": "2026-09-25.1",
473
+ "runner": "deepseek",
474
+ "model": "deepseek-chat",
475
+ "runnerPromptVersion": "2026-09-25.1",
476
+ "recordedAt": "2026-09-25T18:35:50.242Z",
477
+ "judge": "deepseek",
478
+ "judgeModel": "deepseek-chat"
479
+ },
480
+ {
481
+ "schemaVersion": "1.0.0",
482
+ "skillId": "flutter-dart/flutter-testing",
483
+ "strictness": "high",
484
+ "trials": 10,
485
+ "triggerAccuracy": {
486
+ "truePositive": 3,
487
+ "falsePositive": 2,
488
+ "positives": 7,
489
+ "negatives": 7
490
+ },
491
+ "evidence": "authored",
492
+ "scenarios": [
493
+ {
494
+ "id": "trigger-positive-1",
495
+ "kind": "trigger-positive",
496
+ "prompt": "This screen fetches data from a repository and has no test coverage at all",
497
+ "strictness": "high",
498
+ "trials": 1,
499
+ "passes": 0,
500
+ "passRate": 0,
501
+ "passAtK": 0,
502
+ "grader": "trigger-rank-fork-family",
503
+ "status": "ran",
504
+ "deterministic": true
505
+ },
506
+ {
507
+ "id": "trigger-positive-2",
508
+ "kind": "trigger-positive",
509
+ "prompt": "Add a test for this Flutter form that checks the error banner appears on failure",
510
+ "strictness": "high",
511
+ "trials": 1,
512
+ "passes": 1,
513
+ "passRate": 1,
514
+ "passAtK": 1,
515
+ "grader": "trigger-rank-fork-family",
516
+ "status": "ran",
517
+ "deterministic": true
518
+ },
519
+ {
520
+ "id": "trigger-positive-3",
521
+ "kind": "trigger-positive",
522
+ "prompt": "Test this Flutter list widget's loading, data, and empty states",
523
+ "strictness": "high",
524
+ "trials": 1,
525
+ "passes": 1,
526
+ "passRate": 1,
527
+ "passAtK": 1,
528
+ "grader": "trigger-rank-fork-family",
529
+ "status": "ran",
530
+ "deterministic": true
531
+ },
532
+ {
533
+ "id": "trigger-positive-4",
534
+ "kind": "trigger-positive",
535
+ "prompt": "Can you write a test confirming that tapping this button shows the right confirmation banner?",
536
+ "strictness": "high",
537
+ "trials": 1,
538
+ "passes": 0,
539
+ "passRate": 0,
540
+ "passAtK": 0,
541
+ "grader": "trigger-rank-fork-family",
542
+ "status": "ran",
543
+ "deterministic": true
544
+ },
545
+ {
546
+ "id": "trigger-positive-5",
547
+ "kind": "trigger-positive",
548
+ "prompt": "One of our widget tests hangs and times out intermittently, can you get it stable?",
549
+ "strictness": "high",
550
+ "trials": 1,
551
+ "passes": 0,
552
+ "passRate": 0,
553
+ "passAtK": 0,
554
+ "grader": "trigger-rank-fork-family",
555
+ "status": "ran",
556
+ "deterministic": true
557
+ },
558
+ {
559
+ "id": "trigger-positive-6",
560
+ "kind": "trigger-positive",
561
+ "prompt": "Write a unit test for this Flutter view-model that calls a repository",
562
+ "strictness": "high",
563
+ "trials": 1,
564
+ "passes": 0,
565
+ "passRate": 0,
566
+ "passAtK": 0,
567
+ "grader": "trigger-rank-fork-family",
568
+ "status": "ran",
569
+ "deterministic": true
570
+ },
571
+ {
572
+ "id": "trigger-positive-7",
573
+ "kind": "trigger-positive",
574
+ "prompt": "Test this Flutter screen's pull-to-refresh behavior",
575
+ "strictness": "high",
576
+ "trials": 1,
577
+ "passes": 1,
578
+ "passRate": 1,
579
+ "passAtK": 1,
580
+ "grader": "trigger-rank-fork-family",
581
+ "status": "ran",
582
+ "deterministic": true
583
+ },
584
+ {
585
+ "id": "trigger-negative-1",
586
+ "kind": "trigger-negative",
587
+ "prompt": "Write XCTest unit tests for this Swift view model",
588
+ "strictness": "high",
589
+ "trials": 1,
590
+ "passes": 1,
591
+ "passRate": 1,
592
+ "passAtK": 1,
593
+ "grader": "trigger-rank-fork-family",
594
+ "status": "ran",
595
+ "deterministic": true
596
+ },
597
+ {
598
+ "id": "trigger-negative-2",
599
+ "kind": "trigger-negative",
600
+ "prompt": "Add JUnit/Espresso tests for this Kotlin Android screen",
601
+ "strictness": "high",
602
+ "trials": 1,
603
+ "passes": 1,
604
+ "passRate": 1,
605
+ "passAtK": 1,
606
+ "grader": "trigger-rank-fork-family",
607
+ "status": "ran",
608
+ "deterministic": true
609
+ },
610
+ {
611
+ "id": "trigger-negative-3",
612
+ "kind": "trigger-negative",
613
+ "prompt": "Write xUnit tests for this C#/.NET service",
614
+ "strictness": "high",
615
+ "trials": 1,
616
+ "passes": 1,
617
+ "passRate": 1,
618
+ "passAtK": 1,
619
+ "grader": "trigger-rank-fork-family",
620
+ "status": "ran",
621
+ "deterministic": true
622
+ },
623
+ {
624
+ "id": "trigger-negative-4",
625
+ "kind": "trigger-negative",
626
+ "prompt": "Add Jest/React Testing Library tests for this React component",
627
+ "strictness": "high",
628
+ "trials": 1,
629
+ "passes": 1,
630
+ "passRate": 1,
631
+ "passAtK": 1,
632
+ "grader": "trigger-rank-fork-family",
633
+ "status": "ran",
634
+ "deterministic": true
635
+ },
636
+ {
637
+ "id": "trigger-negative-5",
638
+ "kind": "trigger-negative",
639
+ "prompt": "Implement the loading and error states for this Flutter screen",
640
+ "strictness": "high",
641
+ "trials": 1,
642
+ "passes": 0,
643
+ "passRate": 0,
644
+ "passAtK": 0,
645
+ "grader": "trigger-rank-fork-family",
646
+ "status": "ran",
647
+ "deterministic": true
648
+ },
649
+ {
650
+ "id": "trigger-negative-6",
651
+ "kind": "trigger-negative",
652
+ "prompt": "Review this Flutter diff for missing mounted checks",
653
+ "strictness": "high",
654
+ "trials": 1,
655
+ "passes": 1,
656
+ "passRate": 1,
657
+ "passAtK": 1,
658
+ "grader": "trigger-rank-fork-family",
659
+ "status": "ran",
660
+ "deterministic": true
661
+ },
662
+ {
663
+ "id": "trigger-negative-7",
664
+ "kind": "trigger-negative",
665
+ "prompt": "Fix the flutter analyze errors in this Dart file",
666
+ "strictness": "high",
667
+ "trials": 1,
668
+ "passes": 0,
669
+ "passRate": 0,
670
+ "passAtK": 0,
671
+ "grader": "trigger-rank-fork-family",
672
+ "status": "ran",
673
+ "deterministic": true
674
+ },
675
+ {
676
+ "id": "mock-repository-boundary",
677
+ "kind": "behavior",
678
+ "prompt": "This Flutter widget fetches orders through an OrderRepository interface, and OrderRepository's real implementation internally calls a private _parseOrderJson helper to turn the raw response into Order objects. I want to test the widget's loading/data/error states. What should I mock?",
679
+ "strictness": "high",
680
+ "trials": 10,
681
+ "passes": 10,
682
+ "passRate": 1,
683
+ "passAtK": 1,
684
+ "grader": "judge",
685
+ "status": "ran",
686
+ "trialRecords": [
687
+ {
688
+ "output": "Mock the `OrderRepository` boundary — not `_parseOrderJson`, and not anything inside the widget.\n\nConcretely: define (or use the existing) `abstract class OrderRepository` with the method the widget calls (say `Future<List<Order>> fetchOrders()`), and create a mock implementing it — `class MockOrderRepository extends Mock implements OrderRepository {}` with mocktail, or `@GenerateMocks([OrderRepository])` with mockito, matching whatever the project already uses. Then inject that mock wherever the widget gets its repository in the real app (constructor param, provider override, etc.).\n\n```dart\nwhen(() => mockRepo.fetchOrders())\n .thenAnswer((_) async => [Order(id: '1', total: 42.0)]);\n```\n\n`_parseOrderJson` is an internal collaborator of the repository's real implementation, not a boundary the widget depends on. Three reasons to leave it alone:\n\n- It's private, so you can't mock it without reflection hacks or making it public — both bad signals.\n- Mocking it would test \"the widget calls its repository a particular way,\" not \"the widget renders loading/data/error correctly.\" That's the behavior worth asserting.\n- Real refactors of the parsing internals would break the test even though the widget's behavior never changed.\n\nFor the three states you asked about, drive them entirely through the mock:\n\n- Loading: `when(() => mockRepo.fetchOrders()).thenAnswer((_) async => Completer<List<Order>>().future);` then `pumpWidget` and pump one frame — assert the spinner is present before the future completes.\n- Data: `thenAnswer((_) async => [/* orders */])`, `pumpWidget`, `pumpAndSettle()`, assert the rendered `Order` fields appear (`find.text('42.0')`, etc.).\n- Error: `thenAnswer((_) async => throw Exception('network'));`, pump and settle, assert the actual rendered error text (`find.text('Failed to load orders')`), not just that some error path was hit.\n\nTwo things this setup deliberately does not do:\n\n- It doesn't stub `_parseOrderJson`; that helper is exercised by the repository's own unit tests, which can hit the real parsing code with raw JSON fixtures. Keep those separate from the widget tests.\n- It doesn't touch the widget or repository source to make assertions pass — only the test file and its fixtures.\n\nIf you find yourself wanting to intercept `_parseOrderJson` from the widget test, that's the signal to either stub the repository differently or write a repository-level unit test instead.",
689
+ "outputSha256": "85cebeab4f5b4fd860ab42de2a1b94efd70623a4d4d20b7819e1a6df554b7008",
690
+ "promptSha256": "0ae1f4c0ae062f432ebfc971f94920e75e8b24f3730ac3dd0be9b642bdd1bada",
691
+ "deterministic": [],
692
+ "judge": {
693
+ "verdict": "pass",
694
+ "reason": "Answer states to mock OrderRepository boundary, shows concrete mock class `class MockOrderRepository extends Mock implements OrderRepository {}` (and mockito alternative), stubs per state (loading via Completer, data via thenAnswer list, error via throw). Explicitly says _parseOrderJson is internal collaborator not to be mocked, with reasons. No fail criterion met."
695
+ },
696
+ "passed": true
697
+ },
698
+ {
699
+ "output": "Mock `OrderRepository` — the interface the widget depends on — and nothing below it.\n\nThe widget's dependency is `OrderRepository`. That's the external boundary as far as the widget test is concerned: the widget asks its repository for orders and renders whatever comes back (or the error that propagates out). So in the widget test you:\n\n- Define/find the `OrderRepository` abstraction and create a mock against it (`class MockOrderRepository extends Mock implements OrderRepository {}` for mocktail, or the generated mock for mockito).\n- Stub the call the widget makes (`when(() => mock.fetchOrders()).thenAnswer((_) async => [order1, order2])` for data, `...thenThrow(SomeException())` for error, or a never-completing/repeating future pattern to hold the loading state).\n- Inject that mock the same way the real app injects the real implementation (constructor param, provider override), then `pumpWidget`.\n- Assert the three states: loading right after the initial `pump()` (spinner/placeholder present), then data or error after the stub resolves plus a `pump()`/`pumpAndSettle()`.\n\nDo **not** mock `_parseOrderJson`. Three reasons:\n\n1. **It's private and internal to `OrderRepository`'s real implementation.** A widget test that reaches into it is testing that the widget (or the repo) calls its own internal helper a particular way, not that the widget renders correctly. That assertion breaks on every internal refactor and tells you nothing about behavior.\n2. **It's below the boundary you chose.** Once `OrderRepository` is mocked, `_parseOrderJson` never runs anyway — the mock returns `Order` objects directly. There's nothing to mock; the parsing step is simply out of scope for a widget test.\n3. **It belongs to a different test.** JSON-to-`Order` parsing is a unit concern: test the real `HttpOrderRepository` (or the parse helper, if you can expose it for test) directly with sample raw JSON, happily and with malformed/edge-case payloads. Keep that test separate from the widget test.\n\nSo the split is:\n\n- **Widget test** → mock `OrderRepository`, assert loading/data/error rendering and any interaction-driven state change. Verify only boundary calls that matter (`verify(() => mock.fetchOrders()).called(1)`).\n- **Repository/unit test** → use the real `OrderRepository` implementation with a mocked HTTP client / raw-JSON fixture, and assert `_parseOrderJson`'s behavior through the public method that calls it (correct `Order` fields for valid JSON, thrown/typed error for malformed JSON, empty-list handling for an empty array).\n\nOne caveat worth flagging: if you ever find yourself wanting to mock `_parseOrderJson` to control the widget's error state, that's a signal the parse failure isn't surfacing through a catchable, typed error at the `OrderRepository` boundary. The right fix is to make the repository translate parse failures into a domain error the widget can render — then the widget test just stubs `fetchOrders()` to throw that error, and the parsing test covers the translation.",
700
+ "outputSha256": "0998f07603874f9d03b62e6aab3d35acdbdb0162945706130d5ba0604f8bf975",
701
+ "promptSha256": "0ae1f4c0ae062f432ebfc971f94920e75e8b24f3730ac3dd0be9b642bdd1bada",
702
+ "deterministic": [],
703
+ "judge": {
704
+ "verdict": "pass",
705
+ "reason": "Answer states OrderRepository interface is what to mock (\"Mock `OrderRepository` — the interface the widget depends on\"), shows concrete mocktail mechanism `class MockOrderRepository extends Mock implements OrderRepository {}` with stubs for data/error/loading, and explicitly says do not mock `_parseOrderJson` since it's private/internal and below the boundary. No fail criterion holds."
706
+ },
707
+ "passed": true
708
+ },
709
+ {
710
+ "output": "Mock `OrderRepository` — the **external boundary** the widget depends on — not `_parseOrderJson` or anything else inside the repository's own implementation.\n\nConcretely:\n\n- Define/use the existing `OrderRepository` abstract interface (e.g. `Future<List<Order>> fetchOrders()`).\n- Create `class MockOrderRepository extends Mock implements OrderRepository {}` (mocktail) or the mockito equivalent, and stub `fetchOrders()` to return a controlled `Future` for each state:\n - **loading**: return a `Completer`/future you don't complete yet, `pumpWidget()`, assert the loading indicator is present before completing it.\n - **data**: complete the future with a list of `Order`s, then `pump()`/`pumpAndSettle()`, assert the orders render.\n - **error**: complete the future with an error (`thenThrow(...)` / `completeError`), pump, assert the rendered error message.\n- Inject the mock the same way the real app injects the real implementation — via the widget's constructor parameter or a provider override.\n\nWhy not `_parseOrderJson`:\n\n- It's a **private internal collaborator** of the repository. Mocking it would test that the widget calls its own dependency's helper a particular way, not that the widget produces correct behavior, and it breaks on every internal refactor of the repository.\n- Its behavior is the repository's responsibility. If you want to verify JSON parsing, test the real `OrderRepository` implementation directly in its own unit test, feeding it raw response fixtures over a mocked HTTP-client layer (the actual external boundary one level down) — not through the widget.\n\nSo: one mock type, `OrderRepository`, stubbed three ways. Keep `_parseOrderJson` out of the widget tests entirely.\n\nOne caveat to fold in: for the loading-state test, don't use `pumpAndSettle()` before completing the future — it won't settle while the future is pending. Use a plain `pump()` to render the loading frame, assert, then complete the future and `pump()` again for the resolved state.",
711
+ "outputSha256": "211725be898f82af9e07e14d9d1768163de5985640e325e150cef24ba4147d52",
712
+ "promptSha256": "0ae1f4c0ae062f432ebfc971f94920e75e8b24f3730ac3dd0be9b642bdd1bada",
713
+ "deterministic": [],
714
+ "judge": {
715
+ "verdict": "pass",
716
+ "reason": "Answer states OrderRepository is the external boundary to mock (\"Mock `OrderRepository` — the external boundary the widget depends on\"), shows concrete mechanism `class MockOrderRepository extends Mock implements OrderRepository {}` stubbed for loading (Completer), data (list of Orders), error (thenThrow/completeError), and explicitly says not to mock `_parseOrderJson` since it's a private internal collaborator. No fail criterion holds."
717
+ },
718
+ "passed": true
719
+ },
720
+ {
721
+ "output": "Mock `OrderRepository` itself — the interface the widget depends on — and stub its methods (e.g. `fetchOrders()`) with `mocktail`/`mockito`. Do not mock or reference `_parseOrderJson` at all.\n\nThe reason: `_parseOrderJson` is an internal implementation detail of the real `HttpOrderRepository`. It's private, it's not part of the interface the widget sees, and the widget has no knowledge of it. Mocking it would couple your test to the repository's internals — the test would break on every internal refactor of the repository, and worse, it would test that the repository calls its own helper rather than testing the widget's behavior. The widget only cares whether `fetchOrders()` returns data, throws, or is still pending; how the real implementation builds those `Order` objects is the repository's own concern and belongs in that repository's own unit tests, not here.\n\nConcretely, for the widget's three states:\n\n- **Loading:** stub `fetchOrders()` with a `Future` you control and don't complete (or complete it after the first assertion). After `pumpWidget()`, assert the loading indicator is present with a single `pump()`.\n- **Data:** stub `when(() => mockRepo.fetchOrders()).thenAnswer((_) async => [order1, order2])`. `pumpWidget()`, then `pump()` / `pumpAndSettle()` to let the resolved future flush, then assert the rendered order content by text.\n- **Error:** stub `thenAnswer((_) async => throw OrderFetchException())` (or whatever the interface's error contract is), pump, then assert the actual error message text — not just that an error branch was hit.\n\nInject that mock the same way the real app injects the real repository — constructor parameter or provider override — wrapped in whatever ancestors the widget needs (`MaterialApp`, provider scope).\n\nIf you find yourself wanting to mock `_parseOrderJson`, that's the signal you've drifted from testing the widget into testing the repository's internals. If you specifically want `_parseOrderJson` covered, write a separate unit test that exercises the real `HttpOrderRepository` through its public `fetchOrders()` with a mocked HTTP client feeding it raw JSON — the private helper gets covered as a side effect of testing the real implementation through its public surface, and it stays private.",
722
+ "outputSha256": "8d65ec442231c8b157a8629910661f126073217f5cd5fb8de2a3f41c159d6817",
723
+ "promptSha256": "0ae1f4c0ae062f432ebfc971f94920e75e8b24f3730ac3dd0be9b642bdd1bada",
724
+ "deterministic": [],
725
+ "judge": {
726
+ "verdict": "pass",
727
+ "reason": "Answer states to mock OrderRepository itself (\"Mock `OrderRepository` itself — the interface the widget depends on\"), shows concrete mocktail/mockito stubbing per state (loading: uncompleted Future; data: `when(() => mockRepo.fetchOrders()).thenAnswer((_) async => [order1, order2])`; error: `thenAnswer((_) async => throw OrderFetchException())`), and explicitly says not to mock _parseOrderJson since it's an internal detail not the widget's boundary. No fail criterion holds."
728
+ },
729
+ "passed": true
730
+ },
731
+ {
732
+ "output": "Mock `OrderRepository` — the abstract interface the widget depends on — not `_parseOrderJson`.\n\n`_parseOrderJson` is an internal collaborator of the real repository implementation. The widget under test never calls it directly; it calls `OrderRepository`. Mocking the interface lets you control what each of the three states receives without touching repository internals:\n\n- **Loading:** stub `fetchOrders()` with a `Completer` (or a `Future` you hold) that hasn't completed yet, `pumpWidget(...)`, then `pump()` — assert the loading indicator is present.\n- **Data:** complete the mocked future with a hand-built `List<Order>`, then `pumpAndSettle()` (or `pump()` past the transition) — assert the order rows render.\n- **Error:** have the mock throw (or complete with an `Error`), `pumpAndSettle()`, then assert the *specific* error text renders, not just that some error branch was hit.\n\nWith mocktail:\n\n```dart\nclass MockOrderRepository extends Mock implements OrderRepository {}\n\ntestWidgets('shows loading then data', (tester) async {\n final repo = MockOrderRepository();\n final completer = Completer<List<Order>>();\n when(() => repo.fetchOrders()).thenAnswer((_) => completer.future);\n\n await tester.pumpWidget(MaterialApp(home: OrderScreen(repository: repo)));\n await tester.pump(); // loading frame\n expect(find.byType(CircularProgressIndicator), findsOneWidget);\n\n completer.complete([Order(id: '1', total: 42)]);\n await tester.pumpAndSettle();\n expect(find.text('Order #1'), findsOneWidget);\n});\n\ntestWidgets('shows error text on failure', (tester) async {\n final repo = MockOrderRepository();\n when(() => repo.fetchOrders()).thenAnswer((_) async => throw Exception('boom'));\n\n await tester.pumpWidget(MaterialApp(home: OrderScreen(repository: repo)));\n await tester.pumpAndSettle();\n expect(find.text('Failed to load orders'), findsOneWidget);\n});\n```\n\nTwo things worth calling out:\n\n- If the widget constructs the real `HttpOrderRepository` internally rather than accepting one, that's the actual design problem blocking testability — you can't mock what the widget builds for itself. Inject the repository (constructor param, provider override) so the same seam the app uses to supply the real impl is the seam the test uses to supply the mock. Changing the widget to add that injection is a legitimate fix; changing it just to make an assertion pass is not.\n- `_parseOrderJson`'s own behavior — malformed JSON, missing fields, type coercion — belongs in a separate unit test of the real repository (or a top-level function if you extract it), where you feed raw response strings and assert on the parsed `Order`. Don't try to cover that through the widget test; the widget doesn't care how the parse happens, only that it got a result or an error.",
733
+ "outputSha256": "9d4ec37f0179a0d56fa57d220e15704c18d075e382cf2578d643a43c4fa483e3",
734
+ "promptSha256": "0ae1f4c0ae062f432ebfc971f94920e75e8b24f3730ac3dd0be9b642bdd1bada",
735
+ "deterministic": [],
736
+ "judge": {
737
+ "verdict": "pass",
738
+ "reason": "Answer states OrderRepository interface is what to mock (\"Mock `OrderRepository` — the abstract interface the widget depends on\"), shows concrete mocktail mechanism `class MockOrderRepository extends Mock implements OrderRepository {}` with per-state stubs (Completer for loading, completed future for data, throw for error), and explicitly says _parseOrderJson is an internal collaborator not to mock, deferring it to a separate repository unit test."
739
+ },
740
+ "passed": true
741
+ },
742
+ {
743
+ "output": "Mock the `OrderRepository` interface itself — not `_parseOrderJson`.\n\n`OrderRepository` is the external boundary the widget actually depends on: the widget calls something like `repository.fetchOrders()` and only cares whether that returns a `Future<List<Order>>` (or throws). You build:\n\n```dart\nclass MockOrderRepository extends Mock implements OrderRepository {}\n```\n\nthen stub the interface method:\n\n```dart\nwhen(() => mockRepo.fetchOrders())\n .thenAnswer((_) async => [orderA, orderB]); // data\nwhen(() => mockRepo.fetchOrders())\n .thenAnswer((_) async => throw Exception('...')); // error\n```\n\nand never resolve the future at all (or use a `Completer` you control) for the loading state.\n\n`_parseOrderJson` is a **private internal collaborator** of the real `HttpOrderRepository`. Three reasons to leave it alone:\n\n1. **It's not reachable.** A leading-underscore member is library-private; a `Mock implements OrderRepository` doesn't even implement it, so there's nothing to stub. Trying to mock it means mocking the class under test's own guts.\n2. **It's the wrong seam.** Mocking `_parseOrderJson` would assert \"the repository calls its own helper a particular way\" rather than \"the widget renders correct loading/data/error states\" — a test that breaks on every internal refactor of the repository without the widget's contract changing.\n3. **The boundary is the contract.** The widget's contract is the `OrderRepository` interface; that's the only thing the widget is allowed to know exists.\n\nConcretely, for your three states, inject the mock the same way the real app injects the real repository (constructor param, provider override, etc.):\n\n```dart\n// loading — future never completes\nfinal completer = Completer<List<Order>>();\nwhen(() => mockRepo.fetchOrders()).thenAnswer((_) => completer.future);\nawait tester.pumpWidget(wrap(OrderScreen(repository: mockRepo)));\nexpect(find.byType(CircularProgressIndicator), findsOneWidget);\n// no pumpAndSettle here — an unresolved future never settles\n\n// data\nwhen(() => mockRepo.fetchOrders()).thenAnswer((_) async => [orderA, orderB]);\nawait tester.pumpWidget(wrap(OrderScreen(repository: mockRepo)));\nawait tester.pumpAndSettle();\nexpect(find.text('Order #1'), findsOneWidget);\n\n// error — assert the real rendered message, not just \"some error branch\"\nwhen(() => mockRepo.fetchOrders()).thenAnswer((_) async => throw Exception('boom'));\nawait tester.pumpWidget(wrap(OrderScreen(repository: mockRepo)));\nawait tester.pumpAndSettle();\nexpect(find.text('Failed to load orders'), findsOneWidget);\n```\n\nTwo things to keep in mind:\n\n- If `OrderRepository` isn't already an abstract interface the real implementation `implements`/`extends`, creating that interface is the prerequisite step — it's what makes the boundary mockable without reaching into internals. Don't mock a concrete `HttpOrderRepository`'s private helper to get around a missing interface.\n- `_parseOrderJson` is legitimately worth testing, but as a **unit test of the real repository class**, feeding it raw JSON through the HTTP-client seam (mock the HTTP client wrapper, not the parser) and asserting it produces correctly-parsed `Order`s. That's a separate test from these widget tests; don't fold it in.",
744
+ "outputSha256": "e0f1fe34d027e6eeed5ad4f4d1330e2a587903115e6d9135fc837a842aa788f2",
745
+ "promptSha256": "0ae1f4c0ae062f432ebfc971f94920e75e8b24f3730ac3dd0be9b642bdd1bada",
746
+ "deterministic": [],
747
+ "judge": {
748
+ "verdict": "pass",
749
+ "reason": "Answer states OrderRepository interface is the boundary to mock (\"Mock the `OrderRepository` interface itself — not `_parseOrderJson`\"), shows concrete mocktail mechanism `class MockOrderRepository extends Mock implements OrderRepository {}` with per-state stubs (data, error, Completer for loading), and explicitly says _parseOrderJson is a private internal collaborator that should not be mocked (\"It's not reachable\", \"wrong seam\"). No fail criterion holds."
750
+ },
751
+ "passed": true
752
+ },
753
+ {
754
+ "output": "Mock the `OrderRepository` interface itself — that's the external boundary the widget depends on. The widget under test shouldn't know or care that the real implementation happens to delegate to a private `_parseOrderJson` helper; that's an internal implementation detail of the repository, not something the widget's state machine depends on.\n\nConcretely:\n\n```dart\nclass MockOrderRepository extends Mock implements OrderRepository {}\n```\n\nThen stub the interface method the widget actually calls:\n\n```dart\nwhen(() => mockRepository.fetchOrders()).thenAnswer((_) async => [order1, order2]);\n```\n\nWhat to mock — and what not to:\n\n- **Mock:** `OrderRepository` (the interface). For loading, stub it to return a `Future` you control (e.g. a `Completer`) so you can assert the loading state before it completes, then complete it and `pump()` for the data state. For the error state, stub it to throw (`thenThrow`) or return a failed `Future`, then assert the rendered error content.\n- **Do not mock:** `_parseOrderJson`. It's private, so you can't reach it from the test file anyway, and even if you could, mocking it would test that the widget calls the repository in a particular way rather than that the widget produces correct behavior — and it would break on every internal refactor of the repository. The JSON-parsing logic is properly tested at the repository level, not through the widget.\n\nThe three states, driven by one mock:\n\n```dart\ntestWidgets('shows loading then data', (tester) async {\n final repo = MockOrderRepository();\n final completer = Completer<List<Order>>();\n when(() => repo.fetchOrders()).thenAnswer((_) => completer.future);\n\n await tester.pumpWidget(MaterialApp(\n home: OrderScreen(repository: repo),\n ));\n expect(find.byType(CircularProgressIndicator), findsOneWidget); // loading\n\n completer.complete([order1, order2]);\n await tester.pump(); // let the future resolve\n expect(find.text('Order #1'), findsOneWidget); // data\n});\n\ntestWidgets('shows error state', (tester) async {\n final repo = MockOrderRepository();\n when(() => repo.fetchOrders()).thenThrow(Exception('boom'));\n\n await tester.pumpWidget(MaterialApp(home: OrderScreen(repository: repo)));\n await tester.pump();\n\n expect(find.text('Failed to load orders'), findsOneWidget); // error content\n});\n```\n\nOne note: if the repository's `fetchOrders` can throw synchronously rather than returning a failed future, wrap the stubbed call so it returns `Future.error(...)` instead, so the widget's `FutureBuilder`/`try-catch` path actually exercises the error branch. Either way, inject the mock the same way the real app injects the real repository (constructor parameter or provider override), and don't touch the widget under test to make these pass.",
755
+ "outputSha256": "a0b7e85e3fb8ca4f3de303c1c5e7af3a1cb936b5467ed804cfafd5d1cabfa9e3",
756
+ "promptSha256": "0ae1f4c0ae062f432ebfc971f94920e75e8b24f3730ac3dd0be9b642bdd1bada",
757
+ "deterministic": [],
758
+ "judge": {
759
+ "verdict": "pass",
760
+ "reason": "Answer states OrderRepository interface is the boundary to mock (\"Mock the OrderRepository interface itself — that's the external boundary\"), shows concrete mechanism (\"class MockOrderRepository extends Mock implements OrderRepository {}\") with per-state stubbing (Completer for loading, thenAnswer for data, thenThrow for error), and explicitly says not to mock _parseOrderJson since it's an internal detail. No fail criterion holds."
761
+ },
762
+ "passed": true
763
+ },
764
+ {
765
+ "output": "Mock the `OrderRepository` interface itself — not `_parseOrderJson`.\n\nHere's the reasoning:\n\n`OrderRepository` is the external boundary the widget depends on. The widget's constructor (or provider injection) takes an `OrderRepository`, calls something like `fetchOrders()`, and renders a state based on the `Future` result. That's the correct thing to mock:\n\n```dart\nclass MockOrderRepository extends Mock implements OrderRepository {}\n\nfinal mockRepo = MockOrderRepository();\nwhen(() => mockRepo.fetchOrders()).thenAnswer((_) async => [order1, order2]);\n```\n\n`_parseOrderJson` is an *internal collaborator* of the real `OrderRepository` implementation — it's private, it's an implementation detail of `HttpOrderRepository`, and the widget under test never knows it exists. Mocking it would:\n\n1. Be impossible to mock cleanly anyway (it's private, so nothing can override it from outside `HttpOrderRepository`).\n2. Test the wrong thing — that the widget calls its dependency in a particular internal way, rather than that the widget produces the right behavior.\n3. Break on every internal refactor of `HttpOrderRepository`, even when behavior is unchanged.\n\nSo the boundary to mock is the `OrderRepository` interface the widget holds a reference to. You stub `fetchOrders()` directly to return already-constructed `Order` objects for the data state, an empty list for the empty state, and a throwing `Future` for the error state:\n\n```dart\n// loading: pumpWidget, then pump() once — assert CircularProgressIndicator\nwhen(() => mockRepo.fetchOrders()).thenAnswer((_) async => [order1, order2]);\nawait tester.pumpWidget(MaterialApp(home: OrderScreen(repository: mockRepo)));\nexpect(find.byType(CircularProgressIndicator), findsOneWidget); // loading\n\nawait tester.pump(); // mocked Future resolves\nexpect(find.text('Order #1'), findsOneWidget); // data\n\n// error\nwhen(() => mockRepo.fetchOrders()).thenAnswer((_) async => throw Exception('boom'));\nawait tester.pumpWidget(MaterialApp(home: OrderScreen(repository: mockRepo)));\nawait tester.pump();\nexpect(find.text('Failed to load orders'), findsOneWidget);\n```\n\n`_parseOrderJson` only matters if you're writing a *unit test for `HttpOrderRepository` itself* — and even then, you'd test it indirectly by mocking the HTTP client it wraps (e.g. `MockClient` from `package:http/testing.dart` or a mocked `HttpClient`), returning a raw JSON string and asserting the parsed `Order`s come out the other end. You'd never reach into the private helper directly.\n\nOne practical prerequisite: if `OrderRepository` isn't already an abstract interface with `HttpOrderRepository` as a separate implementation, introduce one so the widget depends on an abstraction rather than the concrete HTTP class — otherwise there's nothing clean to mock at that boundary.\n\nAnd per the rules: do not modify `OrderScreen` or `HttpOrderRepository` to make these tests pass. If, while writing them, you discover the widget can't be injected with a test double (because it constructs its own `HttpOrderRepository` internally, for example), that's a real design problem worth flagging in your report rather than silently working around it — but fixing it means a genuine refactor to inject the dependency, not a test-only hack.",
766
+ "outputSha256": "e0b591921b2c649aee036185df89975a379331758d6890d6399d9a9e2b3b807e",
767
+ "promptSha256": "0ae1f4c0ae062f432ebfc971f94920e75e8b24f3730ac3dd0be9b642bdd1bada",
768
+ "deterministic": [],
769
+ "judge": {
770
+ "verdict": "pass",
771
+ "reason": "Answer states OrderRepository interface is the boundary to mock (\"Mock the OrderRepository interface itself — not _parseOrderJson\"), shows concrete mocktail mechanism `class MockOrderRepository extends Mock implements OrderRepository {}` with stubs for data/empty/error states, and explicitly says _parseOrderJson is an internal collaborator that should not be mocked. No fail criterion holds."
772
+ },
773
+ "passed": true
774
+ },
775
+ {
776
+ "output": "Mock the `OrderRepository` interface itself — that's the widget's external boundary. Inject a `MockOrderRepository extends Mock implements OrderRepository {}` (mocktail) or a generated mock (mockito) into the widget the same way the real app injects the real implementation (constructor parameter or provider override).\n\nDo NOT mock or attempt to reach into `_parseOrderJson`. That's an internal collaborator of the repository's *real* implementation, not the widget's boundary. The widget never sees it; it only sees the `OrderRepository` interface's methods returning `Order` objects (or throwing/failing). Two reasons this matters:\n\n1. The widget's behavior under test is \"given the repository returns data/loading/error, does the widget render correctly\" — nothing about JSON parsing is the widget's responsibility, so stubbing a parser is testing the wrong layer.\n2. `_parseOrderJson` is private and is an implementation detail of one concrete repository. Mocking it would break the moment that repository is refactored, and it would couple the widget test to code the widget doesn't call.\n\nConcretely, for the three states:\n\n- **Loading:** stub the interface method with a `Completer`-backed `Future` that hasn't completed yet (`when(() => repo.fetchOrders()).thenAnswer((_) => completer.future)`), `pumpWidget`, then assert the loading indicator with `findsOneWidget` after a plain `pump()` (no settle — nothing has resolved).\n- **Data:** stub `when(() => repo.fetchOrders()).thenAnswer((_) async => [fakeOrder])`, `pumpWidget`, then `pumpAndSettle()` (or a bounded `pump()`) and assert the order content renders.\n- **Error:** stub the same call to throw or return a failed `Result`/`Future.error(...)`, then assert the actual rendered error text (e.g. `find.text('Failed to load orders')`), not just that some error branch was hit.\n\nIf you also want coverage of `_parseOrderJson` itself, that belongs in a separate unit test of the *real* repository implementation, where you mock the layer below it (the HTTP client / platform channel wrapper) and let the real parsing run — never mock the private parser directly.",
777
+ "outputSha256": "1f687ccd526789d47f0cbc3f7eb114ec7e1e593b36ced30ba3a87ec5dab74c26",
778
+ "promptSha256": "0ae1f4c0ae062f432ebfc971f94920e75e8b24f3730ac3dd0be9b642bdd1bada",
779
+ "deterministic": [],
780
+ "judge": {
781
+ "verdict": "pass",
782
+ "reason": "Answer states to mock OrderRepository interface (\"Mock the `OrderRepository` interface itself — that's the widget's external boundary\"), shows concrete mocktail mechanism `MockOrderRepository extends Mock implements OrderRepository {}` with per-state stubs (Completer for loading, thenAnswer async for data, throw/Future.error for error), and explicitly says not to mock `_parseOrderJson` since it's an internal collaborator, not the widget's boundary. No fail criterion holds."
783
+ },
784
+ "passed": true
785
+ },
786
+ {
787
+ "output": "Mock the `OrderRepository` interface itself — that is the external boundary the widget depends on. The widget talks to `OrderRepository`, so that is the seam you substitute with a mock (`MockOrderRepository extends Mock implements OrderRepository` in mocktail, or `@GenerateMocks([OrderRepository])` in mockito) and stub `fetchOrders()` to control what the widget sees.\n\nDo **not** mock `_parseOrderJson`. It's a private helper *inside* the real repository implementation, not something the widget ever touches. The widget never calls it, so a mock of it wouldn't even be reachable from the widget under test, and asserting the widget \"called it a particular way\" would just be testing an internal implementation detail that breaks on every repository refactor without changing observable widget behavior. `_parseOrderJson` (and the JSON-to-`Order` mapping in general) belongs in the repository's own tests, if it has them.\n\nConcretely, for the three states:\n\n```dart\nclass MockOrderRepository extends Mock implements OrderRepository {}\n\nWidget wrap(OrderRepository repo) => MaterialApp(\n home: OrderScreen(repository: repo), // injected the same way the app does it\n );\n\ntestWidgets('shows loading then data', (tester) async {\n final repo = MockOrderRepository();\n // hold the Future so the loading frame is observable, then release it\n final completer = Completer<List<Order>>();\n when(() => repo.fetchOrders()).thenAnswer((_) => completer.future);\n\n await tester.pumpWidget(wrap(repo));\n expect(find.byType(CircularProgressIndicator), findsOneWidget); // loading state\n\n completer.complete([Order(id: '1'), Order(id: '2')]);\n await tester.pumpAndSettle(); // let the future resolve and the list rebuild\n\n expect(find.text('Order 1'), findsOneWidget); // data state\n});\n\ntestWidgets('shows error state', (tester) async {\n final repo = MockOrderRepository();\n when(() => repo.fetchOrders()).thenThrow(Exception('network'));\n\n await tester.pumpWidget(wrap(repo));\n await tester.pumpAndSettle();\n\n expect(find.text('Failed to load orders'), findsOneWidget); // assert actual content\n});\n```\n\nTwo things to keep in mind:\n\n- Because `fetchOrders()` is mocked, it resolves whenever *you* tell it to (the `Completer` above), not on a wall-clock delay. Do not `await Future.delayed(...)` to give it \"time\" — drive it with the mock and `pump()`/`pumpAndSettle()`. For the loading assertion specifically, holding the future open with a `Completer` is the reliable way to observe that frame before it resolves.\n- `_parseOrderJson` never enters these widget tests at all, since the mock returns real `Order` objects directly. That's correct — the widget test's job is to verify the widget renders loading/data/error given what the repository hands back; parsing correctness is a separate unit test that belongs against the real repository, ideally with a mocked HTTP client underneath it.",
788
+ "outputSha256": "61636ccf1673c7469498996a9c3da66125b323c4849587da313f880a015c89ed",
789
+ "promptSha256": "0ae1f4c0ae062f432ebfc971f94920e75e8b24f3730ac3dd0be9b642bdd1bada",
790
+ "deterministic": [],
791
+ "judge": {
792
+ "verdict": "pass",
793
+ "reason": "Answer states OrderRepository interface is the boundary to mock (\"Mock the OrderRepository interface itself — that is the external boundary\"), shows concrete mocktail/mockito mechanism (MockOrderRepository extends Mock implements OrderRepository, @GenerateMocks) with stubs for loading (Completer), data, and error (thenThrow). Explicitly says do not mock _parseOrderJson since widget never calls it. No fail criterion met."
794
+ },
795
+ "passed": true
796
+ }
797
+ ]
798
+ },
799
+ {
800
+ "id": "pump-loading-then-settle",
801
+ "kind": "behavior",
802
+ "prompt": "This Flutter widget shows a loading spinner while a Future resolves, then shows the data. I want a widget test that verifies the loading spinner actually appears before the data does. How should I structure the pump calls?",
803
+ "strictness": "high",
804
+ "trials": 10,
805
+ "passes": 10,
806
+ "passRate": 1,
807
+ "passAtK": 1,
808
+ "grader": "judge",
809
+ "status": "ran",
810
+ "trialRecords": [
811
+ {
812
+ "output": "The key insight: to observe the loading state, you must assert **between** `pumpWidget()` and letting the mocked Future complete — but a `Future` that's already completed will resolve on the very next `pump()`, so you need to control exactly when it resolves.\n\nHere's the structure:\n\n```dart\ntestWidgets('shows loading spinner before data', (tester) async {\n final completer = Completer<List<Order>>();\n when(() => mockRepository.fetchOrders()).thenAnswer((_) => completer.future);\n\n await tester.pumpWidget(\n MaterialApp(home: OrderScreen(repository: mockRepository)),\n );\n\n // First frame: the Future is still pending, so we're in loading state.\n // A single pump() advances one frame without waiting for the Future.\n await tester.pump();\n\n expect(find.byType(CircularProgressIndicator), findsOneWidget);\n expect(find.text('Order A'), findsNothing);\n expect(find.byType(OrderList), findsNothing);\n\n // Now let the mocked Future resolve, then pump again to rebuild.\n completer.complete([Order('A')]);\n await tester.pump(); // rebuild with data\n\n expect(find.byType(CircularProgressIndicator), findsNothing);\n expect(find.text('Order A'), findsOneWidget);\n});\n```\n\nWhy this works:\n\n- **`Completer.thenAnswer`** gives you the explicit handle to decide *when* the Future completes. If you use `thenAnswer((_) async => [...])` instead, the Future resolves on a microtask that may flush before you can observe the spinner, making the loading assertion flaky or impossible.\n- **`tester.pump()`** advances a single frame. It does *not* wait for pending Futures to settle (that's what `pumpAndSettle()` does — and you should avoid it here, because it would skip right past the loading state).\n- **`expect(CircularProgressIndicator, findsOneWidget)`** immediately after `pumpWidget` + `pump()` is the actual assertion that the spinner is rendered while the data is not.\n\nTwo things to watch for:\n\n1. **Don't reach for `pumpAndSettle()` to assert the loading state.** It runs frames until nothing is scheduled, which means the mocked Future completes and the spinner is gone by the time it returns. `pumpAndSettle()` is for *after* you've already verified loading and want to confirm the resolved state fully settles (e.g. after a transition animation finishes).\n\n2. **If your screen shows a transition animation** from spinner to list, the second `pump()` may need to be `pumpAndSettle()` instead — after `completer.complete(...)`, do `await tester.pumpAndSettle()` to let the swap animation finish before asserting the list is present and the spinner is gone.\n\nIf you also want to guard against the spinner never appearing at all (e.g., someone \"optimizes\" by rendering data first), the loading assertion belongs *before* the `complete()` call — that's the whole point of the Completer approach: you're asserting on the one frame where the state is genuinely pending.\n\nIf the Future is stubbed to resolve synchronously (already-completed Future or `SynchronousFuture`), no structure of `pump()` calls will let you observe the spinner — you'll need to switch the stub to a `Completer` as above. That's usually the reason a loading-state test \"doesn't work.\"",
813
+ "outputSha256": "afeca31fe9aff3a476a54bfe390ab2a53ac5e471ea79b0b31612ad5b679cb176",
814
+ "promptSha256": "793e5cf891a3dd143675e5a4fd695c8f4da064eced16ee2dd856efc8b4209f79",
815
+ "deterministic": [],
816
+ "judge": {
817
+ "verdict": "pass",
818
+ "reason": "Answer shows pumpWidget() then a single pump() asserting CircularProgressIndicator before completer.complete() (criterion 1), then completer.complete() + another pump()/pumpAndSettle() asserting data (criterion 2), and explains pumpAndSettle() would skip past the loading state (criterion 3). No fail criterion: pumpAndSettle is not used immediately after pumpWidget as the only step."
819
+ },
820
+ "passed": true
821
+ },
822
+ {
823
+ "output": "Structure it so your mock boundary Future stays unresolved across the first `pump()`, then resolves before the second. The key point: the loading spinner assertion has to happen while the mocked `Future` is still pending — a mocked `Future` only completes when the test tells it to, not on a wall-clock delay.\n\nHere's the shape:\n\n```dart\ntestWidgets('shows loading spinner before data', (tester) async {\n final completer = Completer<List<Order>>();\n when(() => mockRepository.fetchOrders()).thenAnswer((_) => completer.future);\n\n await tester.pumpWidget(\n MaterialApp(home: OrderScreen(repository: mockRepository)),\n );\n\n // Future is still pending — only the loading state should exist.\n await tester.pump(); // let initState's future kick off / first frame build\n expect(find.byType(CircularProgressIndicator), findsOneWidget);\n expect(find.text('Order #1'), findsNothing);\n\n // Now resolve the boundary and let the widget rebuild.\n completer.complete([Order(id: 1)]);\n await tester.pump(); // schedule the setState from the completed future\n await tester.pump(); // build the resolved frame\n\n expect(find.byType(CircularProgressIndicator), findsNothing);\n expect(find.text('Order #1'), findsOneWidget);\n});\n```\n\nThe mechanics that matter:\n\n**Hold the future open.** Use a `Completer` (or a `Future` you never complete in that test) as the mock's `thenAnswer` return. If your mock returns `Future.value([...])` directly, the future is already resolved by the time the first frame builds and you can never observe the loading state.\n\n**First `pump()` — assert loading.** After `pumpWidget`, the widget builds its initial frame with the future unresolved. One additional `pump()` flushes the microtasks that start the fetch without letting the (still-pending) future complete. Assert the spinner with `findsOneWidget` and assert the data with `findsNothing`, so a widget that renders both at once still fails.\n\n**Complete, then `pump()` twice.** `completer.complete(...)` resolves the future, but the `.then`/`setState` callback runs as a microtask. The first `pump()` after completing drains that microtask and schedules the rebuild; the second `pump()` renders the new frame. One `pump()` alone often leaves you looking at the old frame — this is the single most common reason a \"loading → data\" test flakes or falsely passes.\n\n**Don't reach for `pumpAndSettle()` here.** This test is specifically about ordering two distinct frames, so an unbounded settle would blur exactly what you're trying to observe. `pumpAndSettle()` is for \"everything pending should finish\" assertions, not \"did the intermediate state render.\" It also times out against a perpetually-spinning indicator.\n\nOne caveat if the spinner is an endlessly looping animation: it won't prevent these bounded `pump()` calls from working, but never switch this test to `pumpAndSettle()` — a repeating animation means it can never settle and will hit the timeout.",
824
+ "outputSha256": "ecaa49260921c39c55ab29fbcec90583a2510171b6a13824b8ebc5baf49e9369",
825
+ "promptSha256": "793e5cf891a3dd143675e5a4fd695c8f4da064eced16ee2dd856efc8b4209f79",
826
+ "deterministic": [],
827
+ "judge": {
828
+ "verdict": "pass",
829
+ "reason": "Answer shows pumpWidget then a single pump() asserting CircularProgressIndicator findsOneWidget while Completer future is pending (criterion 1), then completer.complete + two pump() calls asserting data state separately (criterion 2), and explains pumpAndSettle would blur/skip the intermediate loading frame and time out (criterion 3). No immediate pumpAndSettle after pumpWidget."
830
+ },
831
+ "passed": true
832
+ },
833
+ {
834
+ "output": "Structure the test so you control the `Future` yourself rather than relying on wall-clock timing, then pump in discrete steps.\n\n## The core idea\n\nA mocked `Future` resolves only when the test tells it to. So build a `Completer` (or have your mock return a `Future` you hold a handle to), pump one frame to capture the loading state, then complete the future and pump again for the data state.\n\n```dart\ntestWidgets('shows spinner while loading, then data', (tester) async {\n final completer = Completer<List<Order>>();\n\n when(() => mockRepo.fetchOrders()).thenAnswer((_) => completer.future);\n\n await tester.pumpWidget(\n MaterialApp(home: OrderScreen(repository: mockRepo)),\n );\n\n // Frame 1: future still pending -> loading state.\n expect(find.byType(CircularProgressIndicator), findsOneWidget);\n expect(find.text('Order A'), findsNothing);\n\n // Resolve the future, then advance a frame.\n completer.complete([Order('A')]);\n await tester.pump(); // rebuild after the future completes\n\n expect(find.byType(CircularProgressIndicator), findsNothing);\n expect(find.text('Order A'), findsOneWidget);\n});\n```\n\n## Why this ordering works\n\n- **`pumpWidget` itself pumps one frame.** The widget's `initState` fires, the `Future` is started but stays pending, and the first build renders the loading state. That is your \"spinner appears\" assertion — no extra `pump()` needed before it.\n- **`completer.complete(...)` schedules the `.then`/`setState`**, but the widget hasn't rebuilt yet. The following `await tester.pump()` flushes that microtask and rebuilds — that's your \"data appears\" assertion.\n- Assert the spinner is **gone** in the second step (`findsNothing`), not just that the data is present. Otherwise a widget that renders a spinner and data at the same time would pass.\n\n## When to use `pump` vs `pumpAndSettle`\n\n- Use a plain `pump()` here because you're stepping the timeline deliberately. There's no infinite animation; you just need the one rebuild triggered by the completed future.\n- If completing the future triggers a fade-in or route transition you want to finish before asserting, use `pumpAndSettle()` after `completer.complete(...)` instead.\n- Never `await Future.delayed(...)` to \"give it time\" — with a `Completer` the timing is fully deterministic, and a wall-clock delay is the classic source of flaky widget tests.\n\n## Two pitfalls to avoid\n\n- **Don't assert the spinner before any pump.** `pumpWidget` must run first for the widget tree to exist; the spinner assertion goes after it, not before.\n- **Don't use `pumpAndSettle()` to observe the loading state.** It waits until nothing is pending — by which point the future has resolved and you'll never see the spinner. If your loading widget runs any repeating animation (a pulsing placeholder), `pumpAndSettle()` will hang; assert with `pump()` and a bounded `pump(duration)` instead.\n- **If the initial `Future` is created in `initState` behind a `FutureBuilder`**, the same structure holds: `pumpWidget` gives you `ConnectionState.waiting`, and completing the future plus one `pump()` gives you `ConnectionState.done`.\n\nThe key discipline: the `Completer` makes the *test* the clock, so `pump()` calls map one-to-one onto the state transitions you're asserting.",
835
+ "outputSha256": "c96e8e5c68a70f753b097fd1c5d0a638562b64e83d7ded2d0c6124112597998b",
836
+ "promptSha256": "793e5cf891a3dd143675e5a4fd695c8f4da064eced16ee2dd856efc8b4209f79",
837
+ "deterministic": [],
838
+ "judge": {
839
+ "verdict": "pass",
840
+ "reason": "Answer shows pumpWidget() then asserts spinner (frame 1, future pending), then completer.complete + await tester.pump() to assert data (findsNothing spinner, findsOneWidget 'Order A'). It explicitly explains pumpAndSettle() waits until nothing is pending so the future resolves and you never see the spinner. All three pass criteria met; no fail criterion."
841
+ },
842
+ "passed": true
843
+ },
844
+ {
845
+ "output": "Structure the test around an explicitly controlled `Future` (a `Completer`, not a real async call), so you — not wall-clock time — decide when the spinner resolves. That's the only way `pump()` vs `pumpAndSettle()` becomes meaningful: you can assert the intermediate loading frame before anything settles.\n\n**The shape of it:**\n\n```dart\ntestWidgets('shows a spinner before the data arrives', (tester) async {\n final completer = Completer<List<Order>>();\n\n final mockRepo = MockOrderRepository();\n when(() => mockRepo.fetchOrders()).thenAnswer((_) => completer.future);\n\n await tester.pumpWidget(\n MaterialApp(home: OrderScreen(repository: mockRepo)),\n );\n\n // First frame: the Future is still pending -> loading state.\n await tester.pump(); // <-- one frame, nothing has resolved yet\n expect(find.byType(CircularProgressIndicator), findsOneWidget);\n expect(find.text('Order #1'), findsNothing);\n\n // Now let the mocked Future complete, then advance the frame.\n completer.complete([Order(id: 1, label: 'Order #1')]);\n\n await tester.pump(); // <-- one more frame: setState/future-builder rebuilds\n expect(find.byType(CircularProgressIndicator), findsNothing);\n expect(find.text('Order #1'), findsOneWidget);\n});\n```\n\n**Why each pump call is where it is:**\n\n- `await tester.pumpWidget(...)` — builds the widget. `Completer.future` is pending, so the first build renders the loading branch. (This initial `pumpWidget` already pumps one frame; the explicit `pump()` after it is belt-and-suspenders and harmless, but the key is that you assert *before* completing the completer.)\n- `await tester.pump()` at the loading assertion — advances a single frame so any pending microtasks scheduled during the first build run, while your Future stays deliberately unresolved. Note you don't strictly need a `FutureBuilder` if the widget calls `setState` on completion — either way, until `completer.complete()` is called, there is nothing to settle.\n- `completer.complete(...)` — resolves the mocked boundary. This schedules the `.then`/`.whenComplete` continuation as a microtask; it has *not* run yet.\n- `await tester.pump()` after completion — drains that microtask queue and rebuilds with the resolved data.\n\n**Key discipline points:**\n\n1. **Do not use `pumpAndSettle()` for the loading assertion.** `pumpAndSettle()` pumps until no frames are scheduled — which, with a pending `Completer`, either hangs until your test timeout, or (if it resolves during the pump) blows straight past the loading state you're trying to catch. The loading state only exists between two specific `pump()` calls; you must assert there.\n\n2. **You can use `pumpAndSettle()` for the final state if there's a transition**, e.g. an `AnimatedSwitcher` fading spinner → data:\n\n ```dart\n completer.complete(orders);\n await tester.pumpAndSettle(); // lets the fade finish, then asserts\n ```\n\n That's fine here because by now there's a real finite animation to settle. For the loading frame itself, stick to a bare `pump()`.\n\n3. **No `Future.delayed` anywhere.** The spinner appears because the completer is unresolved, not because you waited — so the test is deterministic and fast regardless of machine speed.\n\n4. **If the widget uses a `Stream` instead of a `Future`**, use a `StreamController` and drive it the same way: `add()` then `pump()`, assert, close the controller in `tearDown` or at the end to avoid \"a Timer is still pending\" failures.\n\n5. **Assert the spinner by type, the data by content.** `find.byType(CircularProgressIndicator)` for the loading state; `find.text('Order #1')` for the resolved state — asserting on real rendered content means a wrong message still fails the test.\n\n**Mocking:** stub the repository interface the widget takes as a constructor parameter (or a provider override), not any internal helper the widget calls. With mocktail:\n\n```dart\nclass MockOrderRepository extends Mock implements OrderRepository {}\nwhen(() => mockRepo.fetchOrders()).thenAnswer((_) => completer.future);\n```\n\nThat's the boundary; the widget's own internals are never mocked, so internal refactors don't break the test.",
846
+ "outputSha256": "13c0bf4cd66af29f0adaec7cbd177ab0d3d9c2c1ded2ff71ccfc0c83f7f575ca",
847
+ "promptSha256": "793e5cf891a3dd143675e5a4fd695c8f4da064eced16ee2dd856efc8b4209f79",
848
+ "deterministic": [],
849
+ "judge": {
850
+ "verdict": "pass",
851
+ "reason": "Answer shows pumpWidget() then a single pump() asserting CircularProgressIndicator before completer.complete() (criterion 1), then completes the completer and pumps again to assert data (criterion 2), and explains pumpAndSettle() would blow past the loading state / hang with a pending Completer (criterion 3). No fail criterion: pumpAndSettle is only used after completion."
852
+ },
853
+ "passed": true
854
+ },
855
+ {
856
+ "output": "To verify the spinner appears *before* the data, you have to control the `Future` yourself rather than let it resolve on its own — otherwise by the time `pumpWidget` returns, the future may already be complete and you'll never observe the loading frame.\n\nThe structure is: hand the widget a `Future` you own (a `Completer`), pump one frame, assert the spinner, then complete the future and pump again.\n\n```dart\ntestWidgets('shows spinner until the data future resolves', (tester) async {\n final completer = Completer<List<Order>>();\n final repo = MockOrderRepository();\n when(() => repo.fetchOrders()).thenAnswer((_) => completer.future);\n\n await tester.pumpWidget(\n MaterialApp(home: OrdersScreen(repository: repo)),\n );\n\n // The future is still pending -> initState's loading state is on screen.\n await tester.pump(); // let the first frame build\n expect(find.byType(CircularProgressIndicator), findsOneWidget);\n expect(find.text('Order #1'), findsNothing);\n\n // Now resolve the future and let the resulting setState rebuild.\n completer.complete([Order(id: '1', label: 'Order #1')]);\n await tester.pump(); // process the completed future + rebuild\n\n expect(find.byType(CircularProgressIndicator), findsNothing);\n expect(find.text('Order #1'), findsOneWidget);\n});\n```\n\nKey points on the pump calls:\n\n- **`pumpWidget` already pumps one frame.** That frame is usually the loading state, but adding an explicit `await tester.pump()` right after makes the assertion robust against a widget that kicks off its fetch in `initState` and only reflects `isLoading: true` on the *next* build.\n- **Do NOT use `pumpAndSettle()` before asserting the spinner.** `pumpAndSettle` pumps repeatedly until no frames are scheduled — with a pending future and an active spinner animation, on a `CircularProgressIndicator` it will either time out (the indicator animates forever) or, if the future resolves quickly, skip straight past the loading state you're trying to catch. That's exactly the flake you're trying to avoid.\n- **Complete the future, then `pump()` once** (or a couple of times if a transition is involved) to let the `setState` from the resolved future rebuild. If your data appears via an animation/route transition you *can* `pumpAndSettle()` at that point, because by then the only pending work is the finite transition — the infinite spinner is gone.\n- **Never** reach for `await Future.delayed(...)` to \"let the future resolve.\" The mock resolves when you call `completer.complete`, not on wall-clock time.\n\nIf the widget never stalls (it shows data immediately), that's a signal the future is being resolved eagerly or the loading state isn't actually gated on the future — check `initState`/the `FutureBuilder` wiring before adjusting the test. And as always: don't tweak the widget to make the test pass unless the test caught a genuine bug, in which case call it out rather than silently patching the implementation.",
857
+ "outputSha256": "93ef8ab2fcafa77faa7bbd1edb8e7119aa1739ffbdaa63acacb6639cc7e54d35",
858
+ "promptSha256": "793e5cf891a3dd143675e5a4fd695c8f4da064eced16ee2dd856efc8b4209f79",
859
+ "deterministic": [],
860
+ "judge": {
861
+ "verdict": "pass",
862
+ "reason": "Answer shows pumpWidget then a single pump() asserting CircularProgressIndicator before the Completer resolves (criterion 1), then completer.complete + another pump() asserting data (criterion 2), and explains pumpAndSettle would skip past the loading state or time out on the infinite spinner (criterion 3). No fail criterion: pumpAndSettle is not used immediately after pumpWidget."
863
+ },
864
+ "passed": true
865
+ },
866
+ {
867
+ "output": "Structure it as two separate assertion points around one controlled, incomplete `Future` — that's the key: you can only observe the loading state if the mock's `Future` hasn't completed yet.\n\n**The shape of the test:**\n\n1. Make the mocked boundary return a `Completer`'s future (or a mocktail/mockito stub that returns a future you control), *not* a future that's already resolved. This is what lets the loading state be a real intermediate frame instead of a state that flashes past before your first assertion.\n2. `await tester.pumpWidget(...)` with the mocked dependency injected (constructor param or provider override, same as the real app).\n3. **First assertion — loading:** immediately after `pumpWidget`, `pump()` once so the initial frame (spinner) is laid out, then assert `find.byType(CircularProgressIndicator)` `findsOneWidget` and the data widget `findsNothing`. Do *not* call `pumpAndSettle()` here — it would advance past the loading state.\n4. **Resolve the future:** complete the `Completer` (or complete the controlled mock), then `await tester.pump()` again so the resulting `setState`/rebuild renders the data frame. You may need one more `pump()` if the widget transitions through an animation.\n5. **Second assertion — data:** now assert the data widget `findsOneWidget` and the spinner `findsNothing`.\n\n**Why this ordering matters:**\n\n- The loading spinner is only observable while the future is pending. If the mock completes synchronously (a `Future.value(...)` stub), the very first `pump()` already has the data resolved and the spinner never renders — the test can't catch the loading state at all, and it'll pass/fail for the wrong reason.\n- `pumpAndSettle()` waits until there's no pending frame work, which means it will run straight through loading → data. It's the right call *after* you've completed the future and want the data state fully settled; it's the wrong call for catching the intermediate loading frame.\n\n**Concretely:**\n\n```dart\ntestWidgets('shows spinner before data loads', (tester) async {\n final completer = Completer<List<Order>>();\n when(() => mockRepo.fetchOrders()).thenAnswer((_) => completer.future);\n\n await tester.pumpWidget(MaterialApp(home: OrderScreen(repo: mockRepo)));\n\n // 1. Loading state — future still pending\n expect(find.byType(CircularProgressIndicator), findsOneWidget);\n expect(find.byType(OrderList), findsNothing);\n\n // 2. Resolve and render the data frame\n completer.complete([Order(id: 1)]);\n await tester.pump(); // rebuild with data\n\n expect(find.byType(CircularProgressIndicator), findsNothing);\n expect(find.byType(OrderList), findsOneWidget);\n});\n```\n\n**Things to watch for:**\n\n- If `pumpWidget` alone doesn't show the spinner (some widgets kick off the fetch in `initState` and show loading only after the first build), an extra `pump()` between `pumpWidget` and the first assertion is the fix — still no `pumpAndSettle()` there.\n- If the spinner is inside an animated transition (fade/scale), a single `pump()` may not have it fully on screen; assert on presence via `find.byType` rather than opacity, or step with a bounded `pump(Duration(milliseconds: 100))` rather than `pumpAndSettle()`.\n- Mock the repository/HTTP boundary, not the widget's private loading flag — the test should verify the observable spinner, not that some internal `_isLoading` bool was set.\n- Assert the *rendered* data content (e.g. a known order's text), not just that some list exists, so a wrong-data bug still fails the test.",
868
+ "outputSha256": "cc40e775d705f7cf907d9834c3908b6e03ab374b501e258aa7356c30643e4ef6",
869
+ "promptSha256": "793e5cf891a3dd143675e5a4fd695c8f4da064eced16ee2dd856efc8b4209f79",
870
+ "deterministic": [],
871
+ "judge": {
872
+ "verdict": "pass",
873
+ "reason": "Step 3 shows pumpWidget then a single pump() to assert CircularProgressIndicator findsOneWidget while the Completer future is pending. Step 4/5 advances again (completer.complete + await tester.pump()) to assert data state separately. Explicitly explains pumpAndSettle() 'will run straight through loading → data' and is wrong for catching the intermediate frame. No fail criterion: pumpAndSettle is not used immediately after pumpWidget."
874
+ },
875
+ "passed": true
876
+ },
877
+ {
878
+ "output": "Structure it around a **controlled mock future**, not wall-clock time. The key: pump once *before* the mocked future completes to catch the loading frame, then let it complete and pump again for the data.\n\nRough shape:\n\n```dart\ntestWidgets('shows spinner, then data', (tester) async {\n final completer = Completer<List<Order>>();\n when(() => mockRepo.fetchOrders()).thenAnswer((_) => completer.future);\n\n await tester.pumpWidget(\n MaterialApp(home: OrderScreen(repository: mockRepo)),\n );\n\n // Frame 1: future still pending -> loading spinner should be up\n await tester.pump(); // let the initial build/initState future kick off\n expect(find.byType(CircularProgressIndicator), findsOneWidget);\n expect(find.text('Order A'), findsNothing);\n\n // Resolve the mock and advance\n completer.complete([Order('A')]);\n await tester.pump(); // schedule the setState from the completed future\n await tester.pump(); // build the frame with the resolved data\n\n // Data state: spinner gone, data present\n expect(find.byType(CircularProgressIndicator), findsNothing);\n expect(find.text('Order A'), findsOneWidget);\n});\n```\n\nKey points:\n\n- **Use a `Completer`** (or a stub that returns a future you control) so you decide exactly when the future resolves. Never `Future.delayed` or a real wait.\n- **First `pump()` after `pumpWidget`** — this both triggers the async work (via `initState`/`FutureBuilder`) and renders the pending frame. If your widget kicks off the future in `initState`, a single `await tester.pump()` is usually enough; if it needs a microtask to flush, a second bare `pump()` before asserting the spinner is harmless.\n- **Assert the spinner AND the absence of data** in that first state — `findsOneWidget` for the spinner plus `findsNothing` for the resolved content is what actually proves ordering. Just checking the spinner alone doesn't confirm the data hadn't already shown.\n- **After completing**, `await tester.pump()` once to process the `setState`, then again to render. Or `await tester.pumpAndSettle()` if there's an animation on the transition — but a plain `pump()` is the more precise choice here and avoids settling on anything unexpected.\n- **Don't use `pumpAndSettle()` for the loading assertion** — while the future is pending there's nothing to settle, and it would just hang or time out.\n\nIf the spinner is animated (which `CircularProgressIndicator` is), that's fine — you're only reading the widget tree, not waiting for the animation, so plain `pump()` calls work. Only reach for `pumpAndSettle()` once the future has resolved and you want its entrance animation finished before asserting on the data.",
879
+ "outputSha256": "3e09b72f06f7f1d67b5c49ef44638a843db32975df6a24aee73fbf6f3663de0b",
880
+ "promptSha256": "793e5cf891a3dd143675e5a4fd695c8f4da064eced16ee2dd856efc8b4209f79",
881
+ "deterministic": [],
882
+ "judge": {
883
+ "verdict": "pass",
884
+ "reason": "Answer shows pumpWidget then a single pump() to assert spinner (find.byType(CircularProgressIndicator) findsOneWidget) before completer.complete, then advances again with pump()/pumpAndSettle to assert data (find.text('Order A') findsOneWidget). It explains pumpAndSettle immediately after pumpWidget would skip/hang over the pending loading frame ('nothing to settle... hang or time out'). No fail criterion holds."
885
+ },
886
+ "passed": true
887
+ },
888
+ {
889
+ "output": "Structure it as a single `testWidgets` that checks state at two distinct moments — but the key trick is that the mocked `Future` must not resolve until *you* tell it to. If you `await` a real future or let the mock resolve eagerly, you'll never get a frame where the spinner is still on screen, and any `pump` sequence you write is just racing timing.\n\n## The shape of it\n\n```dart\ntestWidgets('shows spinner while loading, then data', (tester) async {\n final completer = Completer<List<Order>>();\n\n // Stub the boundary so the Future stays pending until we complete it.\n when(() => mockRepo.fetchOrders()).thenAnswer((_) => completer.future);\n\n await tester.pumpWidget(\n MaterialApp(home: OrderScreen(repository: mockRepo)),\n );\n\n // Frame 1: Future has not resolved. Spinner must be present.\n await tester.pump(); // let initState's future kick off / first build settle\n expect(find.byType(CircularProgressIndicator), findsOneWidget);\n expect(find.text('Order 1'), findsNothing);\n\n // Now resolve the Future and let the widget rebuild.\n completer.complete([Order(id: '1', name: 'Order 1')]);\n await tester.pump(); // process the microtask that sets state\n await tester.pump(); // render the rebuilt frame with data\n\n expect(find.byType(CircularProgressIndicator), findsNothing);\n expect(find.text('Order 1'), findsOneWidget);\n});\n```\n\n## Why this structure\n\n**A `Completer` instead of a resolved mock.** This is the whole point. `thenAnswer((_) async => [...])` or `thenReturn(future)` resolves too quickly to reliably observe the loading frame — you'd be betting on frame scheduling, which is exactly the flakiness you're trying to avoid. Holding the Future open with a `Completer` makes \"still loading\" an explicit, controlled state rather than a race.\n\n**The first `pump()` after `pumpWidget`.** `pumpWidget` itself already pumps one frame, so depending on where the Future is kicked off (usually `initState`), the spinner may already be visible after `pumpWidget` alone. The extra `pump()` is harmless and covers the case where a microtask needs to drain before the loading state is set. If you find the spinner is already there without it, you can drop it — but keeping it is the safe default.\n\n**Two `pump()` calls after `complete()`.** The first drains the microtask that runs your `.then(setState)`, the second renders the frame that reflects the new state. This is the standard \"settle the async state change\" pattern and avoids `pumpAndSettle()` entirely — which you should *not* use here, because `pumpAndSettle` would blow straight through the loading state you're trying to assert on. You can also collapse these into `await tester.pump()` followed by `await tester.pump(Duration.zero)`, but two bare pumps is clearest.\n\n## Assertions\n\nAssert on **both** sides of the transition, not just that the spinner eventually disappears:\n\n- Loading frame: spinner `findsOneWidget`, data `findsNothing`.\n- Loaded frame: spinner `findsNothing`, data `findsOneWidget`.\n\nChecking \"spinner present AND data absent\" at the loading moment is what actually proves the spinner appears *before* the data, rather than just coexisting with it or flashing by.\n\n## Things that will make this flaky\n\n- `await Future.delayed(...)` anywhere to \"wait for\" the future — a `Completer` resolves when you say, not on wall-clock time; a delay just reintroduces the race.\n- `pumpAndSettle()` between `pumpWidget` and the loading assertion — it waits for all async work to finish, so you'll skip the loading frame entirely.\n- `findsWidgets` instead of `findsOneWidget` if the tree could have more than one spinner ancestor; be specific so a second spinner elsewhere fails loudly.",
890
+ "outputSha256": "4f005d44e78d3e24f0c2736767e75cd2ef6c5944747768eaf6aad2867c4ec73b",
891
+ "promptSha256": "793e5cf891a3dd143675e5a4fd695c8f4da064eced16ee2dd856efc8b4209f79",
892
+ "deterministic": [],
893
+ "judge": {
894
+ "verdict": "pass",
895
+ "reason": "Answer shows pumpWidget then a single pump() asserting CircularProgressIndicator findsOneWidget with data findsNothing before completer.complete (criterion 1). After completer.complete it does two further pump() calls then asserts spinner gone/data present (criterion 2). It explicitly explains pumpAndSettle would 'blow straight through the loading state' (criterion 3). No fail criterion: pumpAndSettle is only warned against, not used."
896
+ },
897
+ "passed": true
898
+ },
899
+ {
900
+ "output": "The key insight: **don't let the Future resolve on its own before you've asserted the spinner.** Control the completion yourself with a `Completer`, so there's no race between the Future resolving and your first assertion.\n\n```dart\ntestWidgets('shows spinner before data', (tester) async {\n final completer = Completer<List<Order>>();\n when(() => mockRepository.fetchOrders()).thenAnswer((_) => completer.future);\n\n await tester.pumpWidget(\n MaterialApp(home: OrderScreen(repository: mockRepository)),\n );\n\n // 1. First frame only: the Future has NOT completed yet, so the\n // widget should be in its loading state.\n await tester.pump();\n expect(find.byType(CircularProgressIndicator), findsOneWidget);\n expect(find.text('Order #1'), findsNothing); // data not there yet\n\n // 2. Now resolve the Future and let the microtask + rebuild run.\n completer.complete([Order(id: 1)]);\n await tester.pump(); // process the completed future + rebuild\n\n // 3. Data replaces the spinner.\n expect(find.byType(CircularProgressIndicator), findsNothing);\n expect(find.text('Order #1'), findsOneWidget);\n});\n```\n\n## Why each pump call is there\n\n- **`await tester.pumpWidget(...)`** builds the widget tree and runs the initial build. In `initState` you kicked off the Future, but a `Completer` whose future you control hasn't resolved, so the widget renders its loading branch. You do *not* need a separate `pump()` just for the initial frame — `pumpWidget` already pumps one frame — but adding an explicit `pump()` right after is harmless and makes the \"assert the loading state\" step self-evident.\n\n- **`expect` the spinner here, before completing the Completer.** This is the whole point of the test: you're asserting the loading state exists at the moment when the Future is genuinely still pending. If you used `pumpAndSettle()` at this stage it would hang or throw a timeout, because a pending Future never lets the tree \"settle.\"\n\n- **`completer.complete(...)`** resolves the Future. It does so asynchronously (microtask), so a single `await tester.pump()` afterwards is what flushes the microtask queue and rebuilds with the resolved data. One `pump()` is usually enough for a `FutureBuilder`/`setState` after a plain Future; use `pumpAndSettle()` instead if resolving the data triggers an animated transition (a `FadeTransition`, a `Navigator` push, etc.).\n\n## Things that will break this structure\n\n- **Don't `await` a real `Future.delayed`** to \"give the Future time.\" A `Completer` resolves when you say so; a wall-clock delay only makes the test slow and flaky and can even let the wrong frame be asserted.\n- **Don't call `pumpAndSettle()` while the Completer is still unresolved.** Nothing settles while a spinner is animating and a Future is pending — it'll spin until timeout.\n- **If the spinner is an unbounded animation** (e.g. a `CircularProgressIndicator`), it genuinely never settles, so even after data loads, prefer bounded `pump(duration)` over `pumpAndSettle()` for any later steps in the same test.\n\n## If the Future is created internally (no injected dependency)\n\nThen you can't control completion directly, and you can't reliably prove the spinner appeared — the Future may resolve before your first assertion if it's already complete (e.g. resolving synchronously from a cache). The robust fix is to inject the boundary (a repository interface) and hand it a `Completer`-backed future, as above. Asserting a loading state that you can't gate is the classic source of \"it passed on my machine\" flakiness.",
901
+ "outputSha256": "b80c597f752be20492d2921b440b4cd4aa6d918b992705c1c936ad3c61f6c7b0",
902
+ "promptSha256": "793e5cf891a3dd143675e5a4fd695c8f4da064eced16ee2dd856efc8b4209f79",
903
+ "deterministic": [],
904
+ "judge": {
905
+ "verdict": "pass",
906
+ "reason": "Answer shows pumpWidget() then a single pump() asserting CircularProgressIndicator before completer.complete() (criterion 1), then completes the Completer and pumps again to assert data (criterion 2). It explains pumpAndSettle() at the loading stage would hang/timeout since a pending Future never settles, skipping the loading frame (criterion 3). No fail criterion holds."
907
+ },
908
+ "passed": true
909
+ },
910
+ {
911
+ "output": "Here's the structure you want — the key idea is that you control the mocked Future, so you can pump exactly one frame with the Future still pending, assert the spinner, then let it complete and pump again.\n\n**The pitfall:** if you call `pumpAndSettle()` right after `pumpWidget()`, it will pump frames until the Future has resolved and the data is on screen — so you'd assert against the data state and never actually observe the spinner. To catch the loading frame you must use a single `pump()`, not `pumpAndSettle()`, at the point where the Future is still pending.\n\n**Structure:**\n\n```dart\ntestWidgets('shows loading spinner, then data', (tester) async {\n // 1. Create a Completer so the mocked Future stays unresolved\n // until you explicitly complete it.\n final completer = Completer<List<Order>>();\n\n when(() => mockRepository.fetchOrders())\n .thenAnswer((_) => completer.future);\n\n // 2. pumpWidget schedules the first frame; the Future is\n // still pending because nothing has completed the completer.\n await tester.pumpWidget(\n MaterialApp(home: OrderScreen(repository: mockRepository)),\n );\n\n // 3. A single pump() renders the frame AFTER initState/first build\n // ran -- i.e. the loading state -- without waiting for the Future.\n await tester.pump();\n\n expect(find.byType(CircularProgressIndicator), findsOneWidget);\n expect(find.text('Order #1'), findsNothing);\n\n // 4. Now resolve the Future, then pump to flush the resulting setState.\n completer.complete([Order(id: '1')]);\n await tester.pump(); // processes the microtask + rebuild\n\n expect(find.byType(CircularProgressIndicator), findsNothing);\n expect(find.text('Order #1'), findsOneWidget);\n});\n```\n\n**Why each pump does what it does:**\n\n- **After `pumpWidget`** — the widget tree is built and `initState` (and any `FutureBuilder`/repository call kicked off there) has run. The Future is pending because you control the `Completer`.\n- **First `pump()`** — advances one frame. This is what makes the loading frame observable. Some codebases actually don't even need this explicit `pump()` if the first build already renders the spinner synchronously; if your `expect` passes without it, that's fine — but including it is harmless and makes intent explicit.\n- **After `completer.complete(...)`** — you need another `pump()` to flush the microtask that resolves the Future and the subsequent `setState`/rebuild. A single `pump()` is usually enough because a completed `Future` schedules its `.then` as a microtask, which drains before the next frame. If your widget also runs a transition (e.g. `AnimatedSwitcher` between states), use `pumpAndSettle()` here instead so the animation finishes before you assert.\n- **`pumpAndSettle()`** — only appropriate at step 4 (and only if there's an animation), never at step 2, because it would skip past the loading frame you're trying to assert.\n\n**A few things to watch:**\n\n- If your widget uses `FutureBuilder` rather than managing state itself, the same structure applies — the `FutureBuilder` sees `ConnectionState.waiting` until the completer resolves, which renders whatever your `waiting` builder returns.\n- If the loading state itself has an animation (a pulsing shimmer, a fading spinner), avoid `pumpAndSettle()` at the loading assertion — a repeating animation makes `pumpAndSettle()` hang. Use a bounded `pump(duration)` sized to one animation step instead.\n- Assert on the spinner's specific type or a `Key` rather than a generic `find.byType(Widget)` so an unrelated refactor doesn't silently make the test pass against the wrong widget.\n\nThat ordering — `pumpWidget` → `pump` → assert loading → `complete` → `pump`/`pumpAndSettle` → assert data — is the canonical way to prove the spinner actually appears before the data, rather than just asserting both states exist at some point.",
912
+ "outputSha256": "7beffc7b025335029344f81da0cdf940fb8139fe946486b8735c5b39bf91b137",
913
+ "promptSha256": "793e5cf891a3dd143675e5a4fd695c8f4da064eced16ee2dd856efc8b4209f79",
914
+ "deterministic": [],
915
+ "judge": {
916
+ "verdict": "pass",
917
+ "reason": "Answer shows pumpWidget() then a single pump() with a pending Completer, asserts CircularProgressIndicator (step 3), then completes the Future and pumps again to assert data (step 4). It explicitly explains pumpAndSettle() right after pumpWidget() would skip the loading frame and assert against data instead. All criteria met."
918
+ },
919
+ "passed": true
920
+ }
921
+ ]
922
+ }
923
+ ],
924
+ "verdict": "fail",
925
+ "scope": "bundled",
926
+ "skillDigest": "53aa478a5f81e1b1172a423262726dfc079d25f3659ae010e60184945b114ac9",
927
+ "catalogDigest": "97f9af01aafac82ae21a63c6af2a2f24fcfe067dc32a7cfdcde9a69a91fa9aae",
928
+ "judgePromptVersion": "2026-09-25.1",
929
+ "runner": "deepseek",
930
+ "model": "deepseek-chat",
931
+ "runnerPromptVersion": "2026-09-25.1",
932
+ "recordedAt": "2026-09-25T18:37:55.230Z",
933
+ "judge": "deepseek",
934
+ "judgeModel": "deepseek-chat"
935
+ },
936
+ {
937
+ "schemaVersion": "1.0.0",
938
+ "skillId": "flutter-dart/flutter-code-review",
939
+ "strictness": "high",
940
+ "trials": 10,
941
+ "triggerAccuracy": {
942
+ "truePositive": 6,
943
+ "falsePositive": 4,
944
+ "positives": 7,
945
+ "negatives": 7
946
+ },
947
+ "evidence": "authored",
948
+ "scenarios": [
949
+ {
950
+ "id": "trigger-positive-1",
951
+ "kind": "trigger-positive",
952
+ "prompt": "Does this diff use the BuildContext safely after the async call finishes?",
953
+ "strictness": "high",
954
+ "trials": 1,
955
+ "passes": 1,
956
+ "passRate": 1,
957
+ "passAtK": 1,
958
+ "grader": "trigger-rank-fork-family",
959
+ "status": "ran",
960
+ "deterministic": true
961
+ },
962
+ {
963
+ "id": "trigger-positive-2",
964
+ "kind": "trigger-positive",
965
+ "prompt": "Take a look at whether this change cleans up its controllers properly",
966
+ "strictness": "high",
967
+ "trials": 1,
968
+ "passes": 0,
969
+ "passRate": 0,
970
+ "passAtK": 0,
971
+ "grader": "trigger-rank-fork-family",
972
+ "status": "ran",
973
+ "deterministic": true
974
+ },
975
+ {
976
+ "id": "trigger-positive-3",
977
+ "kind": "trigger-positive",
978
+ "prompt": "Review this Dart pull request for bang-operator misuse",
979
+ "strictness": "high",
980
+ "trials": 1,
981
+ "passes": 1,
982
+ "passRate": 1,
983
+ "passAtK": 1,
984
+ "grader": "trigger-rank-fork-family",
985
+ "status": "ran",
986
+ "deterministic": true
987
+ },
988
+ {
989
+ "id": "trigger-positive-4",
990
+ "kind": "trigger-positive",
991
+ "prompt": "Does this Flutter diff call setState after an await with no mounted check?",
992
+ "strictness": "high",
993
+ "trials": 1,
994
+ "passes": 1,
995
+ "passRate": 1,
996
+ "passAtK": 1,
997
+ "grader": "trigger-rank-fork-family",
998
+ "status": "ran",
999
+ "deterministic": true
1000
+ },
1001
+ {
1002
+ "id": "trigger-positive-5",
1003
+ "kind": "trigger-positive",
1004
+ "prompt": "Review this Flutter widget for business logic inside build()",
1005
+ "strictness": "high",
1006
+ "trials": 1,
1007
+ "passes": 1,
1008
+ "passRate": 1,
1009
+ "passAtK": 1,
1010
+ "grader": "trigger-rank-fork-family",
1011
+ "status": "ran",
1012
+ "deterministic": true
1013
+ },
1014
+ {
1015
+ "id": "trigger-positive-6",
1016
+ "kind": "trigger-positive",
1017
+ "prompt": "Check this Flutter change for lifecycle bugs before merging",
1018
+ "strictness": "high",
1019
+ "trials": 1,
1020
+ "passes": 1,
1021
+ "passRate": 1,
1022
+ "passAtK": 1,
1023
+ "grader": "trigger-rank-fork-family",
1024
+ "status": "ran",
1025
+ "deterministic": true
1026
+ },
1027
+ {
1028
+ "id": "trigger-positive-7",
1029
+ "kind": "trigger-positive",
1030
+ "prompt": "Review this Flutter diff and report any null-safety risks",
1031
+ "strictness": "high",
1032
+ "trials": 1,
1033
+ "passes": 1,
1034
+ "passRate": 1,
1035
+ "passAtK": 1,
1036
+ "grader": "trigger-rank-fork-family",
1037
+ "status": "ran",
1038
+ "deterministic": true
1039
+ },
1040
+ {
1041
+ "id": "trigger-negative-1",
1042
+ "kind": "trigger-negative",
1043
+ "prompt": "Review this Flutter diff and also fix the bugs you find",
1044
+ "strictness": "high",
1045
+ "trials": 1,
1046
+ "passes": 0,
1047
+ "passRate": 0,
1048
+ "passAtK": 0,
1049
+ "grader": "trigger-rank-fork-family",
1050
+ "status": "ran",
1051
+ "deterministic": true
1052
+ },
1053
+ {
1054
+ "id": "trigger-negative-2",
1055
+ "kind": "trigger-negative",
1056
+ "prompt": "Review this SwiftUI diff for @State lifecycle issues",
1057
+ "strictness": "high",
1058
+ "trials": 1,
1059
+ "passes": 0,
1060
+ "passRate": 0,
1061
+ "passAtK": 0,
1062
+ "grader": "trigger-rank-fork-family",
1063
+ "status": "ran",
1064
+ "deterministic": true
1065
+ },
1066
+ {
1067
+ "id": "trigger-negative-3",
1068
+ "kind": "trigger-negative",
1069
+ "prompt": "Review this Jetpack Compose diff for recomposition bugs",
1070
+ "strictness": "high",
1071
+ "trials": 1,
1072
+ "passes": 1,
1073
+ "passRate": 1,
1074
+ "passAtK": 1,
1075
+ "grader": "trigger-rank-fork-family",
1076
+ "status": "ran",
1077
+ "deterministic": true
1078
+ },
1079
+ {
1080
+ "id": "trigger-negative-4",
1081
+ "kind": "trigger-negative",
1082
+ "prompt": "Review this C#/.NET diff for async/await deadlocks",
1083
+ "strictness": "high",
1084
+ "trials": 1,
1085
+ "passes": 1,
1086
+ "passRate": 1,
1087
+ "passAtK": 1,
1088
+ "grader": "trigger-rank-fork-family",
1089
+ "status": "ran",
1090
+ "deterministic": true
1091
+ },
1092
+ {
1093
+ "id": "trigger-negative-5",
1094
+ "kind": "trigger-negative",
1095
+ "prompt": "Review this React diff for missing effect dependencies",
1096
+ "strictness": "high",
1097
+ "trials": 1,
1098
+ "passes": 1,
1099
+ "passRate": 1,
1100
+ "passAtK": 1,
1101
+ "grader": "trigger-rank-fork-family",
1102
+ "status": "ran",
1103
+ "deterministic": true
1104
+ },
1105
+ {
1106
+ "id": "trigger-negative-6",
1107
+ "kind": "trigger-negative",
1108
+ "prompt": "Implement the fix for the missing mounted check in this Flutter screen",
1109
+ "strictness": "high",
1110
+ "trials": 1,
1111
+ "passes": 0,
1112
+ "passRate": 0,
1113
+ "passAtK": 0,
1114
+ "grader": "trigger-rank-fork-family",
1115
+ "status": "ran",
1116
+ "deterministic": true
1117
+ },
1118
+ {
1119
+ "id": "trigger-negative-7",
1120
+ "kind": "trigger-negative",
1121
+ "prompt": "Write widget tests for this Flutter screen's error state",
1122
+ "strictness": "high",
1123
+ "trials": 1,
1124
+ "passes": 0,
1125
+ "passRate": 0,
1126
+ "passAtK": 0,
1127
+ "grader": "trigger-rank-fork-family",
1128
+ "status": "ran",
1129
+ "deterministic": true
1130
+ },
1131
+ {
1132
+ "id": "read-only-mounted-check",
1133
+ "kind": "behavior",
1134
+ "prompt": "Review this Flutter diff: inside a button's onPressed handler, after `final result = await repository.submit(order);`, the very next line calls `ScaffoldMessenger.of(context).showSnackBar(...)` with no check in between. What do you find?",
1135
+ "strictness": "high",
1136
+ "trials": 10,
1137
+ "passes": 10,
1138
+ "passRate": 1,
1139
+ "passAtK": 1,
1140
+ "grader": "judge",
1141
+ "status": "ran",
1142
+ "trialRecords": [
1143
+ {
1144
+ "output": "A finding under the \"BuildContext after an async gap\" category.\n\n**File:line** — the changed line in the button's `onPressed` handler, immediately after `final result = await repository.submit(order);`, where `ScaffoldMessenger.of(context).showSnackBar(...)` is called.\n\n**The pattern** — `context` is held across an `await` and then used (via `ScaffoldMessenger.of(context)`) with no intervening `context.mounted` check. This is the async-gap `BuildContext` misuse case: the widget that owns `context` may be gone by the time `submit(order)` resolves.\n\n**Why it matters** — if the user navigates away, pops the route, or the widget is otherwise unmounted while the submit call is in flight, the callback resumes on a `BuildContext` that is no longer part of the tree. `ScaffoldMessenger.of(context)` will then throw (a `FlutterError` about looking up a deactivated widget's ancestor) — a crash triggered purely by the race between the request completing and the user leaving the screen. Note that this is specifically a `context`-after-await problem, not a `setState` one: a `mounted` check alone does not suffice here, because on a `State` the correct guard for the `context.mounted` variant is `context.mounted`, checked in the same scope right before the `context` use. (If the handler is inside a `State`, `if (!mounted) return;` also works, but `context.mounted` is the general form that is correct in both `State` and `StatelessWidget` callbacks.)\n\n**Fix direction** — insert a guard between the `await` and the `ScaffoldMessenger` call:\n\n```dart\nfinal result = await repository.submit(order);\nif (!context.mounted) return; // or: if (!mounted) return; inside a State\nScaffoldMessenger.of(context).showSnackBar(...);\n```\n\nDo not lose `result` silently: if the intent was to show a snackbar reflecting the submit result, the early return means that feedback is skipped when the user has already left — that is usually correct (the user is not there to see it), but confirm against the intended UX rather than dropping it without thought.\n\n**Classification** — diff-introduced: this is a new `await`/`context` pairing the change adds, not a pre-existing pattern in touched code.\n\nI have not edited anything; the above is the finding and fix direction only.\n\nOne thing to confirm from the surrounding code, not the diff snippet: whether the handler is a closure inside a `StatefulWidget`'s `State` (in which case both `mounted` and `context.mounted` are valid guards) or a `StatelessWidget` callback (in which case only `context.mounted` is available). That distinction changes which guard line to write, but not whether a guard is required.",
1145
+ "outputSha256": "7f02137c608bf24c633f17d6ccc08f4e9dfacb98e4e552356a07f066c897358a",
1146
+ "promptSha256": "4fd796a41f40558d0cde52493fd0a7a71fd97849679270004c64af97067a5baf",
1147
+ "deterministic": [],
1148
+ "judge": {
1149
+ "verdict": "pass",
1150
+ "reason": "Answer identifies the missing context.mounted check between await and ScaffoldMessenger.of(context) (\"context is held across an await... with no intervening context.mounted check\"), explains the concrete risk (widget unmounted/navigated away while submit pending, ScaffoldMessenger.of throws on deactivated widget), and gives the fix direction `if (!context.mounted) return;` as a finding, explicitly stating \"I have not edited anything; the above is the finding and fix direction only.\" No edit claimed."
1151
+ },
1152
+ "passed": true
1153
+ },
1154
+ {
1155
+ "output": "**Finding — unguarded `BuildContext` use after an async gap**\n\nThe handler is (approximately) this:\n\n```dart\nonPressed: () async {\n final result = await repository.submit(order);\n ScaffoldMessenger.of(context).showSnackBar(...); // context used after await\n},\n```\n\n**Pattern:** `ScaffoldMessenger.of(context)` (and the implicit `Navigator`/overlay lookup `showSnackBar` performs internally) is reached after an `await` with no `context.mounted` check in between. Note the `await` doesn't even need to succeed for the risk to exist — if `submit()` throws, the `await` still completes the handler's execution up to that point, and the context is stale either way; a `try/catch` around the await doesn't help unless the check also guards the post-await path.\n\n**Why it matters:** `submit()` is a network-backed call, so the user can navigate away, pop the route, or dispose the enclosing `State` while it is in flight. When the call resolves, `context` no longer refers to a live element tree. `ScaffoldMessenger.of(context)` then throws a `FlutterError` (\"Looking up a deactivated widget's ancestor is unsafe\") or, in some lifecycle states, returns a messenger that is no longer mounted — either way this is a crash-class bug on a path that is easy to hit (submit + back button), not a theoretical one.\n\n**Fix direction (do not apply):** capture the messenger *before* the `await`, or guard with `context.mounted` immediately after it.\n\n```dart\nonPressed: () async {\n final messenger = ScaffoldMessenger.of(context); // before the gap\n final result = await repository.submit(order);\n if (!context.mounted) return; // or use `messenger` unconditionally\n messenger.showSnackBar(...);\n},\n```\n\nCapturing the messenger up front is the more robust form: it is valid up to the point the *route* leaves the tree, and it avoids a second lookup. The `context.mounted` guard alone is correct but only protects the lookup, not a `setState()` or `Navigator.push` that may follow in the same handler — check whether this handler does any of those too, since each would need the same guard.\n\n**Classification:** diff-introduced (the missing check is new in this change).\n\n**Also confirm (not asserted):** if this handler lives in a `State` and any later line calls `setState()`, that call needs a `!!mounted` (State's own) check, distinct from `context.mounted`; and if `repository.submit` returns a value that a subsequent line bang-asserts (`result!.id`), that `!` crosses the same async gap and is a separate null-safety finding. Neither is confirmed from what you've shown — flag only if present in the diff.\n\nNo files modified; this is a report only.",
1156
+ "outputSha256": "5f65bfc6d7bf05dd10d88d8f3f3aff821b2d917c8be4cec9f929d671bd82f9af",
1157
+ "promptSha256": "4fd796a41f40558d0cde52493fd0a7a71fd97849679270004c64af97067a5baf",
1158
+ "deterministic": [],
1159
+ "judge": {
1160
+ "verdict": "pass",
1161
+ "reason": "Answer identifies the missing context.mounted check between await and ScaffoldMessenger.of(context) (Pattern section), explains the concrete risk (user can navigate away/pop route/dispose State while submit in flight, so context is stale and lookup throws FlutterError), and names the fix direction 'if (!context.mounted) return;' right after the await as a finding, explicitly stating 'do not apply' and 'No files modified; this is a report only.' No edit claimed."
1162
+ },
1163
+ "passed": true
1164
+ },
1165
+ {
1166
+ "output": "lib/order/order_screen.dart (button `onPressed` handler) — `ScaffoldMessenger.of(context)` called after `await repository.submit(order)` with no `context.mounted` check in between.\n\nPattern: unguarded `BuildContext` used across an async gap. The handler holds a `BuildContext`, performs an `await`, then dereferences that same context via `ScaffoldMessenger.of(context)` with nothing between the `await` and the use.\n\nWhy it matters: if the user navigates away (or the widget is otherwise unmounted) while `submit` is still in flight, the handler resumes on a `BuildContext` that is no longer mounted. `ScaffoldMessenger.of(context)` then looks up the messenger on a defunct element and throws — typically a `FlutterError` about looking up an ancestor on a deactivated/unmounted widget. This is the same race as a `setState()` after dispose, just surfacing at a different lookup call. The window is smallest when the dialog/snackbar would have been most useful: the user tapping \"back\" right after submitting.\n\nFix direction: insert a guard immediately after the await, before touching `context`:\n\n```dart\nfinal result = await repository.submit(order);\nif (!context.mounted) return; // or `if (!mounted) return;` inside a State\nScaffoldMessenger.of(context).showSnackBar(...);\n```\n\nUse `context.mounted` when the handler only holds a `BuildContext` (e.g. inside a `StatelessWidget` or a builder); use `mounted` when inside a `State`, since the state's own `mounted` is available and equivalent here.\n\nNotes:\n- If `result` is used to build the snackbar text, keep any use of `result` after the guard so the whole post-await block is protected by the single check.\n- This is a finding the diff introduces, assuming this handler is new or newly async in the diff. If the handler predates this diff and only the awaited call changed, treat it as pre-existing in a touched line — flag it, but note the distinction.\n- If the handler also calls `setState()` after this, that call needs the same guard; the single `if (!context.mounted) return;` placed right after the await covers both, provided the `setState` comes after it too.\n\nI have not modified any file — reporting only.",
1167
+ "outputSha256": "16e325e75c6aa792eeac0fadaa9502f2c7887bd474f1cc6390c42001cf213760",
1168
+ "promptSha256": "4fd796a41f40558d0cde52493fd0a7a71fd97849679270004c64af97067a5baf",
1169
+ "deterministic": [],
1170
+ "judge": {
1171
+ "verdict": "pass",
1172
+ "reason": "Answer names the missing context.mounted check between await repository.submit and ScaffoldMessenger.of(context) (criterion 1), explains the risk that navigating away/unmounting during the pending submit leaves a defunct context that throws (criterion 2), and gives the concrete fix `if (!context.mounted) return;` right after the await as a finding, explicitly stating 'I have not modified any file — reporting only' (criterion 3). No edit claimed."
1173
+ },
1174
+ "passed": true
1175
+ },
1176
+ {
1177
+ "output": "Found: `ScaffoldMessenger.of(context)` used after an `await` with no `context.mounted` check in between — a BuildContext-after-async-gap finding.\n\n**Where:** the button's `onPressed` handler, the line immediately following `final result = await repository.submit(order);` (report at its actual file:line).\n\n**What the pattern is:** the handler holds a `BuildContext` (the one it closed over for the button), crosses an `await` at `repository.submit(order)`, and then dereferences that same context via `ScaffoldMessenger.of(context)` on the very next line, with nothing between the await and that use — no `context.mounted` check, and no `mounted` check either.\n\n**Why it matters:** the `await` is a suspension point. If the user navigates away, pops the screen, or the widget is otherwise disposed while `submit` is in flight, the context is no longer mounted by the time the future completes. `ScaffoldMessenger.of(context)` on a defunct context then throws — in current Flutter, a \"Looking up a deactivated widget's ancestor is unsafe\" / deactivated-widget error. This is exactly the async-gap case the skill flags regardless of how fast `submit` usually resolves; \"usually resolves before the user can navigate away\" is not a guarantee.\n\n**Fix direction — do not apply it:** add a `context.mounted` guard immediately after the await and before the `ScaffoldMessenger.of(context)` call, returning early if it is false. Something like:\n\n```dart\nfinal result = await repository.submit(order);\nif (!context.mounted) return;\nScaffoldMessenger.of(context).showSnackBar(...);\n```\n\nNote the guard must sit *between* the await and the context use — a `mounted` check placed before the await does not cover this, since the gap is the await itself.\n\n**Scope note:** this finding is diff-introduced (the unguarded use is on the changed line). If the same handler contains other post-await usages of `context` or a `setState` further down, flag each of those too, since the same gap covers every use after the await, not just the `ScaffoldMessenger` line. Also worth a glance in the same handler: whether a `mounted`/`setState` pairing exists later, and whether the onPressed captures a controller or subscription that this change also starts — but the primary finding here is the ungarded `context.mounted` on the post-await context dereference.\n\nNo source was or should be modified for this — report only.",
1178
+ "outputSha256": "1c600ff0f23cf467b7352a3745bae10866efbeedf2862b604c9bb409dbc30b1e",
1179
+ "promptSha256": "4fd796a41f40558d0cde52493fd0a7a71fd97849679270004c64af97067a5baf",
1180
+ "deterministic": [],
1181
+ "judge": {
1182
+ "verdict": "pass",
1183
+ "reason": "Answer names the missing context.mounted check between the await and ScaffoldMessenger.of(context) (\"no context.mounted check, and no mounted check either\"), explains the disposal/navigation risk (\"user navigates away, pops the screen, or the widget is otherwise disposed while submit is in flight... throws\"), and gives the fix direction \"if (!context.mounted) return;\" while explicitly stating \"do not apply it\" and \"No source was or should be modified... report only.\" No edit claimed."
1184
+ },
1185
+ "passed": true
1186
+ },
1187
+ {
1188
+ "output": "**Finding — `lib/...:onPressed` (the line calling `ScaffoldMessenger.of(context)`): guarded-context violation after an async gap.**\n\n- **Pattern:** Inside the button's `onPressed` handler, `final result = await repository.submit(order);` is followed directly by `ScaffoldMessenger.of(context).showSnackBar(...)` with no check between the `await` and the `context` use.\n- **Risk category:** `BuildContext` used across an async gap (Step 2, \"BuildContext and setState after an async gap\"). The handler no longer runs synchronously with the widget's lifetime: if the user navigates away — or the widget is otherwise disposed — while `submit(order)` is in flight, the widget is unmounted by the time the await resolves. Using `context` then is a \"don't use BuildContext across async gaps\" violation; `ScaffoldMessenger.of(context)` can throw (`Looking up a deactivated widget's ancestor is unsafe`) or reach a stale/detached element, and the snack bar would target the wrong (or no) scaffold.\n- **Important nuance:** `State.mounted` is not the check to reach for here. In a callback that captures a `BuildContext`, the guard is `context.mounted` (available on `BuildContext` since Flutter 3.7). Use `if (!context.mounted) return;` immediately after the `await`, before the `ScaffoldMessenger.of(context)` line. If this handler is inside a `State`, `mounted` would also work, but when the value used is a `BuildContext` the idiomatic, correct guard is `context.mounted`.\n- **Fix direction (not applied):** insert the mounted check between the await and the context use:\n\n```dart\nfinal result = await repository.submit(order);\nif (!context.mounted) return;\nScaffoldMessenger.of(context).showSnackBar(...);\n```\n\n- **Also check the same handler for the sibling pattern:** if there is a `setState(...)` anywhere after this await, it needs the matching `mounted` guard as well — the absence of a check before the `ScaffoldMessenger` call strongly suggests the same handler is missing it for any subsequent `setState`. Confirm by reading the full `onPressed` body, not just the two lines shown.\n- **Provenance:** this is a diff-introduced finding (the await-then-context pattern is in the changed lines). I have not modified any file — this is a report only, per the skill's read-only rule.\n\nOne thing I could not verify from the snippet alone: whether `repository.submit` can complete after disposal in practice (it usually can — the user tapping the button and immediately navigating back is the common trigger). The missing guard is a finding regardless of how unlikely that race feels; \"usually resolves before the user can navigate away\" is not a guarantee.",
1189
+ "outputSha256": "e714fa747450a2b2da84fe7134588dc7112443efe499f31b76c0d0f5669ddfc1",
1190
+ "promptSha256": "4fd796a41f40558d0cde52493fd0a7a71fd97849679270004c64af97067a5baf",
1191
+ "deterministic": [],
1192
+ "judge": {
1193
+ "verdict": "pass",
1194
+ "reason": "Answer identifies the missing context.mounted check between the await and ScaffoldMessenger.of(context) (Pattern bullet), explains the disposal/navigation risk and stale context throw, and names the fix `if (!context.mounted) return;` right after the await as a finding, explicitly stating 'not applied' and 'I have not modified any file'. No edit claimed."
1195
+ },
1196
+ "passed": true
1197
+ },
1198
+ {
1199
+ "output": "**Finding — unguarded `BuildContext` use after an `await`**\n\n`onPressed` handler (file/line unknown from the snippet alone) — `ScaffoldMessenger.of(context)` is called immediately after `final result = await repository.submit(order);`, with no `context.mounted` check between the `await` and the use of `context`.\n\nWhy it matters: the `await` yields control back to the event loop. If the widget that owns this `context` is removed from the tree while `repository.submit(order)` is still in flight — the user backs out of the screen, a parent swaps this route for another, a list rebuild disposes the entry — then by the time the continuation resumes, the element behind `context` is defunct. `ScaffoldMessenger.of(context)` performs a `dependOnInheritedWidgetOfExactType` lookup that walks the element tree from a deactivated element, which throws (a \"looking up a deactivated widget's ancestor\" / `FlutterError` family failure, not a silent no-op). So a caller who navigates away mid-submit turns a successful submission into a crash on the resume path.\n\nThis is the `BuildContext`-after-async-gap case from the focus list, not the `setState` case — the value being used across the gap is `context`, so the required guard is `context.mounted`, not `mounted` (they coincide here because it looks like a `State` method, but `context.mounted` is the check that matches the value actually being dereferenced).\n\nFix direction (do not apply — read-only review):\n\n```dart\nfinal result = await repository.submit(order);\nif (!context.mounted) return;\nScaffoldMessenger.of(context).showSnackBar(...);\n```\n\n`context.mounted` rather than `mounted` is the safer spelling if this handler could ever be extracted off the `State` (a callback wired from a `StatelessWidget` parent has no `mounted` to consult); as written inside a `State` the two are equivalent, so flag whichever matches the surrounding style but note the distinction.\n\nTwo adjacent things worth checking while in this handler, since they follow from the same shape:\n\n- If the very next line after the snackbar is a `setState(...)` (e.g. clearing a loading flag or storing `result`), that `setState` is **also** past the gap and needs the same guard — a single `if (!context.mounted) return;` placed right after the `await` covers both the context use and any following `setState`, so prefer one guard immediately post-`await` over two guards.\n- If the handler entered by flipping a `_isSubmitting` flag via `setState` before the `await`, confirm that flag is also reset on every exit path, not just the success path — otherwise a failed submit leaves the button permanently disabled and the crash-avoidance guard never gets reached anyway. That's a related lifecycle finding, not part of the primary one above, so report it separately if present.\n\n**Scope caveats.** This review is of the snippet you quoted only — I have not read the file, so I can't give a concrete `file:line`, can't confirm whether the surrounding method is a `State` method or a closure over some other `BuildContext`, and can't tell whether this pattern is introduced by the diff or pre-existing in touched code. Treat it as a finding to be located and classified, not a located one. No source was modified.",
1200
+ "outputSha256": "9e5f24e2b7e2f41dce57e07c5d2320b6790f6789e03604484d7e20cbe535c063",
1201
+ "promptSha256": "4fd796a41f40558d0cde52493fd0a7a71fd97849679270004c64af97067a5baf",
1202
+ "deterministic": [],
1203
+ "judge": {
1204
+ "verdict": "pass",
1205
+ "reason": "Answer identifies the missing context.mounted check between the await and ScaffoldMessenger.of(context) (\"no context.mounted check between the await and the use of context\"). Explains risk: widget removed from tree while submit in flight, element defunct, lookup throws. Gives fix direction `if (!context.mounted) return;` after await, explicitly \"do not apply — read-only review\" and \"No source was modified.\" No edit claimed."
1206
+ },
1207
+ "passed": true
1208
+ },
1209
+ {
1210
+ "output": "Finding — unguarded `BuildContext` use after an async gap.\n\n**Pattern:** In the button's `onPressed` handler, `ScaffoldMessenger.of(context).showSnackBar(...)` runs immediately after `final result = await repository.submit(order);` with no `context.mounted` (or `mounted`, if this is inside a `State`) check between the `await` and the `context` use.\n\n**Why it matters:** The `BuildContext` captured by the closure can become invalid while the `await` is in flight — the user navigates away, pops the route, or the widget is otherwise unmounted before `submit` resolves. Once that happens, `ScaffoldMessenger.of(context)` is resolving a lookup against a deactivated/unmounted element. Depending on the Flutter version and where the context sits in the tree, this surfaces as an assertion/exception (\"Looking up a deactivated widget's ancestor is unsafe\", or a `ScaffoldMessenger` not found error), and at minimum it is an unsafe lookup performed against an element that may no longer be mounted. The classic trigger is exactly this shape: submit → user backs out of the screen while the request is pending → response lands → `ScaffoldMessenger.of(context)` fires on a dead context.\n\n**Fix direction (not applied — this review is read-only):**\n- Add a guard on the line immediately after the `await`, before touching `context`:\n - `if (!context.mounted) return;` — when you only have the `BuildContext` (e.g. a stateless widget or a callback that isn't a `State` member).\n - `if (!mounted) return;` — when the handler is inside a `State` subclass; this is the more precise check there.\n- Only then call `ScaffoldMessenger.of(context).showSnackBar(...)`.\n- Note that capturing `ScaffoldMessenger.of(context)` into a local *before* the `await` does not make this safe by itself — the messenger's association with the context is still what is at risk. The guard is the fix.\n\n**Scope note:** This finding is about the diff as described (the `await` and the `context` use are adjacent with nothing between them). If there is in fact a `mounted`/`context.mounted` check on a line you didn't quote — or if the check lives inside a helper the handler delegates to before the snackbar call — then this is a false positive and the code is fine. As quoted, there is no intervening check, so it is a finding.\n\n**If the handler also calls `setState` after this `await`:** the same gap covers it — the `mounted` check must precede both the `setState` and the `ScaffoldMessenger` use, not just one of them.\n\nI did not modify any file; this is a report with fix direction only.",
1211
+ "outputSha256": "58bd78484243d941d0f4e4ef23fdc555639bab0986f4f8eb6bb4f3220f0de547",
1212
+ "promptSha256": "4fd796a41f40558d0cde52493fd0a7a71fd97849679270004c64af97067a5baf",
1213
+ "deterministic": [],
1214
+ "judge": {
1215
+ "verdict": "pass",
1216
+ "reason": "Answer names the missing context.mounted/mounted check between the await and ScaffoldMessenger.of(context) (Pattern section), explains the concrete risk (context invalidated while await in flight, user navigates away/pops route, deactivated widget assertion), and gives the fix direction `if (!context.mounted) return;` right after the await, explicitly stating it is not applied and the review is read-only. No edit claimed."
1217
+ },
1218
+ "passed": true
1219
+ },
1220
+ {
1221
+ "output": "**Finding — `context` used after an `await` with no `context.mounted` check**\n\n**Pattern:** An `onPressed` handler (typically a closure inside a `StatelessWidget` or `State`) runs `final result = await repository.submit(order);` and then, as the very next line, calls `ScaffoldMessenger.of(context).showSnackBar(...)`. There is no intervening `if (!context.mounted) return;` (or, in a `State`, `if (!mounted) return;`) between the `await` and the use of `context`.\n\n**Why it matters:** The `await` yields control of the event loop. The widget that owns this `context` can be unmounted while the `submit` call is in flight — the user navigates away, pops the screen, or the app rebuilds past this route. When the future completes and control resumes, `ScaffoldMessenger.of(context)` is called on a `BuildContext` that is no longer attached to the tree. That throws:\n\n> `Looking up a deactivated widget's ancestor is unsafe.`\n\nIt is the `BuildContext` sibling of the classic `setState() called after dispose()` crash: same async-gap lifecycle bug, different symptom. The specific failure is reachable whenever the user leaves the screen during the submit call — a common, realistic window for a network-round-trip call, not a contrived race.\n\n**Fix direction (do not apply — this review is read-only):** add a mounted guard immediately after the `await`, before the `ScaffoldMessenger` call:\n\n- In a `State` (or `StatefulWidget`'s state): `if (!mounted) return;`\n- In a callback that only holds a `BuildContext` (e.g. a `StatelessWidget`/`Builder` closure): `if (!context.mounted) return;`\n\nThe guard must sit *between* the `await` and the `context` use, not before the `await`.\n\n**Scope note:** Confirm whether this diff *introduces* the unguarded call or the guard was already absent in the code being modified. If the surrounding code is pre-existing and the diff only touches the `submit` line or the snackbar arguments, report it as a pre-existing issue in a touched file, not a diff-introduced one. Either way it is a finding, since the risk category is real regardless of which side of the change introduced it.\n\n**Related checks worth running on the same handler while you are here:**\n- If the same `await` is followed by `setState()` anywhere in this method, that call needs the same guard — flag it together.\n- If a bang operator (`result!`) is used on the value returned from `submit`, verify its non-null status is locally evident; if not, flag it as a separate null-safety risk.\n\n**No code was modified by this review.**",
1222
+ "outputSha256": "78b62772aaeb3e73e4151c2775d3dc69b892913cf8079a58414a9bd8eda846c7",
1223
+ "promptSha256": "4fd796a41f40558d0cde52493fd0a7a71fd97849679270004c64af97067a5baf",
1224
+ "deterministic": [],
1225
+ "judge": {
1226
+ "verdict": "pass",
1227
+ "reason": "Answer identifies the missing context.mounted/mounted guard between the await and ScaffoldMessenger.of(context) (\"There is no intervening `if (!context.mounted) return;`... between the `await` and the use of `context`\"). Explains risk: widget can be unmounted/navigated away during submit, causing \"Looking up a deactivated widget's ancestor is unsafe.\" Names concrete fix: add `if (!context.mounted) return;` (or `if (!mounted) return;`) right after await, and states \"No code was modified by this review.\" No edit claimed."
1228
+ },
1229
+ "passed": true
1230
+ },
1231
+ {
1232
+ "output": "**Finding — unguarded `BuildContext` use after an async gap, `ScaffoldMessenger.of(context)` reached directly after `await repository.submit(order)` with no `context.mounted` check in between.**\n\nThe `onPressed` handler holds a `BuildContext` (the `State`'s `context`, or the builder's context). Between the `await` and the `ScaffoldMessenger.of(context)` call, the widget can be unmounted — the user navigates away, a route is popped, the screen is disposed — while `submit(order)` is still in flight. If that happens, the `context` is no longer mounted when the continuation runs, and `ScaffoldMessenger.of(context)` looks up an ancestor on a defunct element tree. Depending on the surrounding state of the tree this either throws (`Looking up a deactivated widget's ancestor is unsafe` / a `FlutterError` about using a `BuildContext` after the widget has been disposed) or resolves to something stale. Either way it is a crash-or-stale-UI risk, not a stylistic concern.\n\nThe race is not remote: `submit(order)` is a network call, which is exactly the kind of await during which a user backs out of a screen.\n\nFix direction (not applied — this review is read-only): guard the context use immediately after the await, before any use of `context`:\n\n```dart\nfinal result = await repository.submit(order);\nif (!context.mounted) return;\nScaffoldMessenger.of(context).showSnackBar(...);\n```\n\nIf this handler lives in a `State`, `if (!mounted) return;` is the equivalent/idiomatic check; `context.mounted` is the correct one for a `BuildContext` captured in a stateless callback. Note the check must sit between the `await` and the `ScaffoldMessenger.of(context)` call — placing it before the await does nothing.\n\nAlso confirm the same gap does not bite on the `result` handling: if the code touches `setState` or any other `context`-dependent call after the snackbar using the same post-await continuation, that use needs the same guard, not just the snackbar line.\n\n**Scope note:** I only have the snippet you quoted, not the file. If the handler already returns early on unmount earlier in the continuation (before the line shown) or the widget is provably never disposed mid-flight, say so — but from what is quoted, the check is absent. This is a finding introduced by this diff branch, assuming the `await` + snackbar sequence is new or newly touched here; if both lines are pre-existing, classify it as pre-existing in a touched file rather than diff-introduced.",
1233
+ "outputSha256": "019dc32f5ee139d19263d56b8ffee9c9582a4a224af8a8baf37cef93ce29adfa",
1234
+ "promptSha256": "4fd796a41f40558d0cde52493fd0a7a71fd97849679270004c64af97067a5baf",
1235
+ "deterministic": [],
1236
+ "judge": {
1237
+ "verdict": "pass",
1238
+ "reason": "Answer names the missing context.mounted check between await and ScaffoldMessenger.of(context) (finding header, and 'no context.mounted check in between'). Explains risk: widget can be unmounted/navigated away while submit in flight, context defunct, throws or stale. Gives fix direction 'if (!context.mounted) return;' right after await, explicitly 'not applied — this review is read-only'. No edit claimed."
1239
+ },
1240
+ "passed": true
1241
+ },
1242
+ {
1243
+ "output": "lib/order/order_screen.dart — `ScaffoldMessenger.of(context)` used after `await repository.submit(order)` with no `context.mounted` check between the await and the use.\n\n**Pattern:** `BuildContext` use across an async gap. The handler calls `ScaffoldMessenger.of(context)` directly on the line following the `await`, with nothing guarding it in the intervening space.\n\n**Why it matters:** `onPressed` runs as an async callback that can outlive the widget. If the user navigates away, pops the route, or the screen is otherwise unmounted while `submit` is in flight, the `context` is defunct by the time the await resolves. `ScaffoldMessenger.of(context)` then throws a \"Looking up a deactivated widget's ancestor is unsafe\" error (and, depending on the ancestor lookup, a `FlutterError` about an unmounted element). This is a crash/stale-UI risk triggered by a race the button handler has no control over: how long the network round-trip takes versus how fast the user leaves the screen.\n\nNote the same gap would also bite `setState` or `Navigator.of(context)` if they follow this line — the issue is the unchecked `await` boundary, not the specific `ScaffoldMessenger` call.\n\n**Fix direction (not applied):** add a mounted guard immediately after the await, before touching `context`. For a `State` widget's handler:\n\n```dart\nfinal result = await repository.submit(order);\nif (!context.mounted) return; // or `if (!mounted) return;` inside a State\nScaffoldMessenger.of(context).showSnackBar(...);\n```\n\nUse `context.mounted` when the handler only has the `BuildContext` in scope; use `mounted` when it is a method on a `State`. Either way the check must sit between the `await` and the `context` use — a guard anywhere else does not close the gap.\n\n**Diff-introduced vs. pre-existing:** if this handler is new or the `await`/`ScaffoldMessenger` pair is new in this diff, it is a diff-introduced finding. If the await and the snackbar call both pre-date the diff and only surrounding lines changed, note it as pre-existing.\n\nI have not edited the file; this is the finding and its fix direction only.",
1244
+ "outputSha256": "eff0e34e34c10b2bc12675dd8c67da4e1116d060303674fbda4f2ea39a4b7ddd",
1245
+ "promptSha256": "4fd796a41f40558d0cde52493fd0a7a71fd97849679270004c64af97067a5baf",
1246
+ "deterministic": [],
1247
+ "judge": {
1248
+ "verdict": "pass",
1249
+ "reason": "Answer names the missing context.mounted check between await repository.submit(order) and ScaffoldMessenger.of(context) (criterion 1). Explains risk: widget can be unmounted/navigated away while submit is in flight, making context defunct and throwing 'deactivated widget's ancestor is unsafe' (criterion 2). Gives fix direction 'if (!context.mounted) return;' after the await, explicitly 'not applied' and states 'I have not edited the file' (criterion 3). No edit claimed, so no fail criterion holds."
1250
+ },
1251
+ "passed": true
1252
+ }
1253
+ ]
1254
+ },
1255
+ {
1256
+ "id": "undisposed-controller-finding",
1257
+ "kind": "behavior",
1258
+ "prompt": "Review this Flutter diff: a new AnimationController is created in initState() of this StatefulWidget's State, but dispose() is not overridden anywhere in the class. What's wrong?",
1259
+ "strictness": "high",
1260
+ "trials": 10,
1261
+ "passes": 10,
1262
+ "passRate": 1,
1263
+ "passAtK": 1,
1264
+ "grader": "judge",
1265
+ "status": "ran",
1266
+ "trialRecords": [
1267
+ {
1268
+ "output": "An `AnimationController` created in `initState()` with no `dispose()` override anywhere in the class is an undisposed-controller finding, and it's more serious than a style nit.\n\n**What's wrong**\n\n`AnimationController` is one of the Flutter resource-owning objects that must be explicitly released. Creating it in `initState()` establishes its lifetime as \"the lifetime of this `State` object\" — so the matching release has to happen in that same `State`'s `dispose()`. With no `dispose()` override at all, there is no release point:\n\n- The controller (and the `Ticker` it drives) is never cleaned up. A running/animating controller holds a frame callback and keeps the ticker subscribed, so the widget's animation machinery keeps being driven even after the `State` is torn down, and the underlying platform/ticker resources are retained for the app's lifetime rather than the screen's.\n- Flutter's leak tracking (`flutter run --leak-tracking`, and `LeakTracking`/`debugDisposed` assertions on controllers) will flag this `State` as a leaking instance.\n- If the `State` is later recreated (the screen is revisited), a new controller is constructed while the old one is still alive — the leak compounds per visit.\n- A `Ticker` that outlives its `State` can also trigger the framework's \"Ticker was active when its widget was disposed\" class of error, because nothing ever stopped it.\n\nThe absence of any `dispose()` override makes this unambiguous from reading alone — there is no base class or mixin path that could be disposing it, because the class doesn't override the method at all. That is exactly the case where I should *not* soften the finding to \"confirm the base class disposes this\": no `super.dispose()` chain from this class means no cleanup of the controller the class itself created.\n\n**Fix direction (not applied — this skill is read-only)**\n\nAdd an override that releases the controller and chains to super *last*:\n\n```dart\n@override\nvoid dispose() {\n _controller.dispose(); // release this class's own resource first\n super.dispose(); // super call last, not first\n}\n```\n\nTwo ordering points worth stating, since both are separate focus items:\n\n- The controller's `dispose()` goes **before** `super.dispose()`. Disposing the superclass first invalidates state the subclass's cleanup path may still rely on.\n- `super.dispose()` must be the **last** call in the override. A `dispose()` that calls `super.dispose()` first is itself a finding, even when the controller *is* disposed.\n\nIf the controller is also driven through a listener or paired with a `Ticker` that this class subscribes to, those subscriptions must be cancelled in the same `dispose()` — but the controller itself is the primary leak here.\n\n**How I'd report it**\n\n```\nlib/.../foo_screen.dart:<initState line> — AnimationController created in\ninitState() with no dispose() override in the State class. Risk: controller\nand its Ticker are never released; the animation callback can outlive the\nwidget and fires the \"Ticker active when widget disposed\" error class;\nresources leak for the app's lifetime. No super.dispose() chain exists from\nthis class, so nothing else can be releasing it. Fix direction: add\ndispose() that (1) disposes any listeners/subscriptions on the controller,\n(2) calls _controller.dispose(), then (3) calls super.dispose() last.\n```\n\nReporting only the creation site with file/line, the risk category (lifecycle/disposal), and the fix direction — no source file is modified by the review.",
1269
+ "outputSha256": "0a0610382161a067b21868ad40580836ccc410f7a1ee5068334efce266dad16c",
1270
+ "promptSha256": "fb6508511e88612c52cbe4b7a3c46e4859e29c306038cefb3e216da76f0073ef",
1271
+ "deterministic": [],
1272
+ "judge": {
1273
+ "verdict": "pass",
1274
+ "reason": "Answer explicitly states no dispose() override exists ('no dispose() override at all, there is no release point'), explains ticker/controller resources retained for app's lifetime, and gives fix direction as a finding (not applied): override dispose(), call _controller.dispose() then super.dispose() last, shown in code block labeled 'not applied'. No negligible-leak hedge."
1275
+ },
1276
+ "passed": true
1277
+ },
1278
+ {
1279
+ "output": "The problem is a resource leak: an `AnimationController` created in `initState()` is never released, because `dispose()` is not overridden anywhere in the class.\n\n**Why this matters**\n\n`AnimationController` is a `ChangeNotifier`-backed `Animation` that registers itself with the `SchedulerBinding` via a `Ticker` and attaches listeners. It holds onto:\n\n- A `Ticker` that is registered with `SchedulerBinding` and scheduled each frame — it keeps requesting frames for as long as it is running, and the `Ticker` holds a reference back to the widget's `State`.\n- Listeners registered on the controller (e.g. anything in `AnimatedBuilder`/`addListener`), and, depending on construction, platform/vsync resources through the `TickerProvider`.\n\nFailing to call `controller.dispose()` means the ticker is never cancelled and the listeners are never removed. This is not merely a \"minor nit at screen teardown\":\n\n- If the controller is still animating when the `State` is removed from the tree, the ticker keeps the scheduler waking up and keeps the `State` (and everything it closes over) reachable — a memory leak that outlives the screen.\n- Flutter asserts on an active `Ticker` at dispose time in debug builds (\"A Ticker was active … was disposed with an active Ticker\"), and a leaked/disposed controller being used later produces \"AnimationController.dispose() called more than once\" or a ticker-used-after-dispose crash.\n- If this screen is pushed and popped repeatedly, each visit leaks another controller/ticker pair; the leak compounds rather than being bounded.\n\n**The fix direction (not applied — this review is read-only)**\n\nAdd a `dispose()` override to the `State` class that disposes the controller, and call `super.dispose()` **last**:\n\n```dart\n@override\nvoid dispose() {\n _controller.dispose(); // release the ticker + listeners\n super.dispose(); // super last, not first\n}\n```\n\nThis applies to every disposable resource the `State` owns, not just this one: `AnimationController`, `TextEditingController`, `ScrollController`, `FocusNode`, and any `StreamSubscription` created in `initState()` all need a matching release in `dispose()`. If the class currently creates more than one such object, each one must be disposed here.\n\n**One caveat to confirm rather than assume**\n\nBefore calling this a definite leak, confirm that no base class or mixin not shown in the diff already disposes the controller — e.g. a custom base `State` that tracks children, or a mixin whose `dispose()` reaches this instance. If the class extends something other than plain `State` (or mixes in a disposal helper), verify whether the base actually disposes it. If that cannot be determined from the diff, the safe statement is: \"no disposal is visible in this class; confirm the base class disposes the controller.\" Absent such a base-class disposal, this is a real undisposed-controller finding.\n\n**Classification**\n\nThis is a diff-introduced finding (the leak is created by this change), not a pre-existing one, assuming the `initState()` creation and the missing `dispose()` both arrive in this diff. If the controller predates the diff and only its usage changed, note the leak as pre-existing in a touched file instead.\n\n**Summary finding**\n\n- **Location:** the `State` class for this widget — `dispose()` is absent.\n- **Pattern:** `AnimationController` created in `initState()` with no corresponding release.\n- **Risk category:** lifecycle / resource leak (ticker + listeners retained past teardown; debug assertion on active ticker).\n- **Fix direction:** override `dispose()`, call `_controller.dispose()` for each owned disposable, then `super.dispose()` last — pending confirmation that no base class already does this.",
1280
+ "outputSha256": "fd14b3cb29e626ac4fc735e89f37e6eecd9abebc676338563eaf8e9b32361538",
1281
+ "promptSha256": "fb6508511e88612c52cbe4b7a3c46e4859e29c306038cefb3e216da76f0073ef",
1282
+ "deterministic": [],
1283
+ "judge": {
1284
+ "verdict": "pass",
1285
+ "reason": "Answer explicitly states dispose() is not overridden anywhere in the class (criterion 1), explains the ticker keeps SchedulerBinding waking and holds State reachable, leaking for the app's lifetime (criterion 2), and names the fix direction as a finding not applied: override dispose(), call _controller.dispose(), then super.dispose() last (criterion 3). No negligible-dismissal hedge."
1286
+ },
1287
+ "passed": true
1288
+ },
1289
+ {
1290
+ "output": "The problem: an `AnimationController` is created in `initState()` but nothing releases it — `dispose()` is not overridden, so the controller is never disposed. That's a resource-lifecycle finding, and it is diff-introduced since the controller is new in this change.\n\n**Why it matters.** `AnimationController` drives a `Ticker` (from `SingleTickerProviderStateMixin`/`TickerProviderStateMixin`). `AnimationController.dispose()` is what stops that ticker and unregisters from the provider. If it is never called:\n\n- The ticker keeps running and keeps scheduling frame callbacks; the Flutter framework itself asserts on this. In debug mode you get the classic `AnimationController.dispose() called more than once`-adjacent failure or the explicit error: *\"AnimationController was used after being disposed\"* / more commonly the `TickerProviderStateMixin` tear-off assert firing because a ticker is still active when the `State` is removed from the tree — the framework detects the leaked ticker and throws in debug.\n- In release mode it silently leaks: the controller (and its ticker, listeners, and any `Animation` objects hung off it) is retained for the lifetime of the app, not just the screen. Every time the user opens this screen again, another controller leaks.\n- Any listeners attached to the controller (`.addListener`, `.addStatusListener`) also stay alive and can fire against a `State` that is no longer mounted, which is a second crash path on top of the leak.\n\nSo this is a genuine finding, not a style nit. Creating a long-lived object in `initState()` establishes an ownership obligation on the `State`; `dispose()` is where that obligation is honored.\n\n**Fix direction** (report only, do not apply): override `dispose()` in this `State`, call `_controller.dispose()` (along with `.removeListener` first if a named listener was added, since `dispose()` does not remove listeners for you), and call `super.dispose()` **last** — not first. Ordering matters: the framework's base `dispose()` is what tears down the mixin's ticker bookkeeping, so running it before the class's own cleanup can leave the ticker unregistered while the controller still exists.\n\n**One caveat before asserting a leak with certainty.** If this `State` extends a base class or uses a mixin not shown in the diff that owns and disposes the controller (some codebases route controllers through a base `State` or a controller-provider mixin), then `dispose()` being absent from *this* class is not necessarily a leak. Since the controller is created in `initState()` here — a direct, local ownership — that would be unusual, but it is not something reading only this class can rule out. The honest form of the finding is: \"confirm the base class disposes this controller; if nothing else in the hierarchy owns it, this is an undisposed `AnimationController` leaking its ticker.\" If ownership is local, state it as a definite leak.\n\n**Report shape:**\n\n```\nlib/<file>.dart:<initState line> — AnimationController created in initState()\nwith no matching dispose() override anywhere in the State. Risk: ticker leaks\nfor the app's lifetime (framework assert in debug, silent leak in release), and\nany listeners on the controller can fire after the State is unmounted. Fix\ndirection: override dispose(), call _controller.dispose() (remove listeners\nfirst if any were added), then super.dispose() last. Confirm no base class or\nmixin in the hierarchy already owns/disposes this controller.\n```\n\nI have not edited any file — this is findings and fix direction only.",
1291
+ "outputSha256": "90432bc21e306b433799bd4c8c942fa7265deb301e97f2e3e79e8de9309b6cb9",
1292
+ "promptSha256": "fb6508511e88612c52cbe4b7a3c46e4859e29c306038cefb3e216da76f0073ef",
1293
+ "deterministic": [],
1294
+ "judge": {
1295
+ "verdict": "pass",
1296
+ "reason": "Answer states dispose() is not overridden so controller never disposed (criterion 1), explains ticker keeps running/leaks for app lifetime in release and framework assert in debug (criterion 2), and gives fix direction: override dispose(), call _controller.dispose(), super.dispose() last, explicitly 'report only, do not apply' (criterion 3). No negligible-dismissal hedge; the base-class caveat asks to confirm, not dismiss."
1297
+ },
1298
+ "passed": true
1299
+ },
1300
+ {
1301
+ "output": "The problem is a resource leak: the newly created `AnimationController` in `initState()` has no corresponding release in `dispose()`, and since the class does not override `dispose()` at all, nothing ever calls `AnimationController.dispose()` on it.\n\nPer Step 2 of this review (\"Every `TextEditingController`/`AnimationController`/`StreamSubscription`/`FocusNode`/`ScrollController` created in `initState()` (or elsewhere in the `State`) has a matching release in `dispose()` — flag one created with no corresponding disposal\"), this is a finding, not a nit:\n\n- **What it is:** `initState()` creates an `AnimationController`, but there is no `dispose()` override in the `State` class to release it.\n- **Why it matters:** An `AnimationController` is not garbage-collected away when the widget is removed from the tree. Leaving it undisposed leaks its underlying resources for the lifetime of the app, not just the screen. It also means:\n - The `Ticker` that backs the controller keeps running / is never stopped, and `flutter analyze`-level checks like the `dispose` lint (`must_be_disposed` family) typically flag exactly this pattern.\n - If the controller is still animating when the widget is unmounted, the ticker continues driving frames and can throw when its `vsync` (the `TickerProviderStateMixin`/`SingleTickerProviderStateMixin` ticker) outlives the state, and in the `SingleTickerProviderStateMixin` case, creating a second controller later or the mixin's own `dispose` asserting the ticker was disposed can surface as a runtime error.\n - Callbacks/status listeners attached to the controller can fire against a disposed/unmounted `State`, compounding into `setState() called after dispose()`-style failures.\n\n**Fix direction (not applied — this review is read-only):** override `dispose()` in the `State`:\n\n```dart\n@override\nvoid dispose() {\n _controller.dispose(); // release before super\n super.dispose(); // super must be called LAST\n}\n```\n\nTwo ordering points from Step 2 apply to the fix:\n- `super.dispose()` must be called **last**, not first — disposing the controller before `super.dispose()` is correct.\n- If the controller was created with `SingleTickerProviderStateMixin` / `TickerProviderStateMixin`, those mixins' own `dispose()` (reached via `super.dispose()`) assert their tickers have already been disposed, so the ordering matters and the missing override would trip that assertion.\n\n**Confidence note:** Because the diff shows the `State` class with no `dispose()` override, this is a diff-introduced finding — not merely pre-existing code the diff touched. One caveat per the rules: confirm the base class or any mixin in the class hierarchy does not already provide a `dispose()` that releases this controller. If the class extends a custom base `State` subclass that overrides `dispose()` and disposes children controllers, that would need checking before asserting the leak outright. Absent such a base, however, a `State` with an `initState()`-created `AnimationController` and no `dispose()` override is a definite disposal gap.\n\n**Summary:** `initState()` creates an `AnimationController`; the class never overrides `dispose()`, so the controller is never disposed — a resource/ticker leak. Report it as a finding with the fix direction above; do not edit the file.",
1302
+ "outputSha256": "f0e535d2d799d90d3351199aa9cc810877e175c6541eb8f194f35277df8b9454",
1303
+ "promptSha256": "fb6508511e88612c52cbe4b7a3c46e4859e29c306038cefb3e216da76f0073ef",
1304
+ "deterministic": [],
1305
+ "judge": {
1306
+ "verdict": "pass",
1307
+ "reason": "Answer explicitly states the class does not override dispose() at all, so nothing calls AnimationController.dispose() (criterion 1). It explains the ticker/resources leak for the app's lifetime, not just the screen (criterion 2). It names the fix as a finding, not applied: override dispose(), call _controller.dispose() before super.dispose() last, with code shown (criterion 3). No negligible-dismissal hedge."
1308
+ },
1309
+ "passed": true
1310
+ },
1311
+ {
1312
+ "output": "An `AnimationController` created in `initState()` with no `dispose()` override is an undisposed-controller finding.\n\n**What's wrong:** the controller is never released. `AnimationController` owns platform resources (a `Ticker` bound to the widget's `TickerProvider`) and registers listeners on the framework's animation scheduler. Without `controller.dispose()` in a `dispose()` override, that ticker is never cancelled and the controller is never detached — it leaks for the lifetime of the app, not just the widget's on-screen life. On top of the leak, a still-ticking controller can call back into a disposed `State` (e.g. a `setState()` driven by an animation listener, or a `Ticker` firing after the `State` is gone), risking \"setState() called after dispose()\" errors.\n\n**Fix direction (do not apply — this review is read-only):** override `dispose()` in the `State` and call `controller.dispose()` there, before `super.dispose()`, which must be last:\n\n```\n@override\nvoid dispose() {\n _controller.dispose(); // release the controller's ticker/resources\n super.dispose(); // super last, not first\n}\n```\n\n**Caveat before asserting this is a certain leak:** if the class mixes in or extends something not fully shown in the diff (a mixin or base class) that disposes this controller, the leak may not actually exist. In that case the finding is \"confirm the base class/mixin disposes this controller; if it does not, add the dispose() override above\" rather than an assertion that a leak is present. If nothing in the diff or the visible surrounding code disposes it, treat it as a diff-introduced leak.\n\nAlso check the two adjacent lifecycle items while here: any `setState()` reached after an `await` needs a `mounted` check, and if the controller drives an animation whose listener touches `context`, that use needs a `context.mounted` guard after any async gap.",
1313
+ "outputSha256": "7b717d5e79961cf4c50b531a8f0b1ecf9033d6e815bd8c9f8744049bc3531490",
1314
+ "promptSha256": "fb6508511e88612c52cbe4b7a3c46e4859e29c306038cefb3e216da76f0073ef",
1315
+ "deterministic": [],
1316
+ "judge": {
1317
+ "verdict": "pass",
1318
+ "reason": "Answer states controller 'is never released' with no dispose() override, explains ticker 'never cancelled... leaks for the lifetime of the app', and gives fix direction (override dispose(), call _controller.dispose(), super.dispose() last) explicitly as read-only finding. No negligible-leak hedge; the base-class caveat is a scoping note, not dismissal."
1319
+ },
1320
+ "passed": true
1321
+ },
1322
+ {
1323
+ "output": "The problem, per the lifecycle-and-disposal check in Step 2 of this skill:\n\n**What's wrong:** `dispose()` is not overridden in the `State` class, but `initState()` creates an `AnimationController`. That controller is never disposed. Every `AnimationController` created in `initState()` needs a matching release in `dispose()` — this is a real finding, not a nit: an undisposed controller leaks its underlying platform resources (its ticker, and the animation it drives) for the app's lifetime, not just the screen's. Depending on how it's driven, it can also keep animating / firing listeners after the widget is gone, producing stale-UI or `setState() called after dispose()` errors downstream.\n\n**Fix direction (do NOT apply — this skill is read-only):**\n\n```dart\n@override\nvoid dispose() {\n _controller.dispose();\n super.dispose();\n}\n```\n\nTwo details on the fix direction:\n\n- The controller's `dispose()` call belongs **before** `super.dispose()`. Step 2 flags calling `super.dispose()` first as a bug — the class's own cleanup runs before handing off to the superclass.\n- If the controller's value is wired to a widget (e.g. via `AnimatedBuilder` / `ListenableBuilder`) or if any listener was attached in `initState()`, dispose the controller and detach listeners you added.\n\n**Confidence caveat:** the finding as stated (\"a controller is created in `initState()` and no `dispose()` is overridden *anywhere in the class*\") is certain, since the diff shows the controller creation and the absence of an override. But if this class extends a base class or uses a mixin not shown in the diff that could be responsible for disposal, the correct report is \"confirm the base class disposes this\" rather than asserting a leak with no evidence — per the Rules. Given the diff shows no such base-class disposal, report it as a leak finding.\n\n**Report format:**\n\n```\nlib/<file>.dart:<initState line> — AnimationController created in\ninitState() with no matching disposal in dispose() (dispose() is not\noverridden in this State). Risk: leaks the controller's ticker/platform\nresources for the app's lifetime; possible stale-UI or setState-after-\ndispose errors if it drives UI. Fix direction: override dispose(), call\n_controller.dispose() first, then super.dispose() last — do not apply.\n```\n\nThis is a diff-introduced finding (new code in the diff), not a pre-existing issue in merely-touched code.",
1324
+ "outputSha256": "f42f19a127504a31776a0dc5cbfa3a89b1aa69293526666abf0006f1ef7308fe",
1325
+ "promptSha256": "fb6508511e88612c52cbe4b7a3c46e4859e29c306038cefb3e216da76f0073ef",
1326
+ "deterministic": [],
1327
+ "judge": {
1328
+ "verdict": "pass",
1329
+ "reason": "Answer explicitly states dispose() is not overridden while initState() creates the controller, so it's never disposed (criterion 1). It explains the ticker/platform resources leak for the app's lifetime, not just the screen's (criterion 2). It names the fix direction as a finding, not applied: override dispose(), call _controller.dispose() before super.dispose() last, with code shown and 'do NOT apply' (criterion 3). No negligible-leak hedge."
1330
+ },
1331
+ "passed": true
1332
+ },
1333
+ {
1334
+ "output": "The finding: an `AnimationController` is created in `initState()` but the `State` class never overrides `dispose()` to release it. That is a resource leak — the controller and the `Ticker` it drives are never disposed.\n\nWhat's wrong, specifically:\n\n- **The controller is never disposed.** An `AnimationController` owns a `Ticker`, which is registered with the scheduler. Until `dispose()` is called on the controller, it keeps ticking (and the framework keeps its listener/handle alive) for the lifetime of the app, not just the screen. Navigating away from this widget tears down the widget subtree but does **not** stop or release the controller, so you leak the ticker and any associated resources on every push of this screen.\n- **This is a diff-introduced finding, not pre-existing style noise.** The controller creation is new in the diff, and no matching release exists in the class, so the diff introduces the leak.\n- **Severity:** in a screen that can be pushed repeatedly, this accumulates — one leaked ticker per visit. Even a single visit leaks for the app's lifetime. It is a real finding, not a minor nit.\n\nFix direction (do not apply here — this review is read-only): override `dispose()` in the `State`, call `_controller.dispose()` (alongside any other controllers/subscriptions/focus nodes the class owns), and call `super.dispose()` **last**, not first.\n\nTwo things to confirm before asserting the leak is definite:\n\n1. Whether the class inherits from a base class or mixin not shown in the diff that might already dispose this controller — if so, verify rather than assume. If that cannot be determined from what is shown, report it as \"confirm the base class disposes this\" rather than asserting a leak.\n2. Whether the controller is actually created only in this `initState()` path, or also reassigned elsewhere in the `State` — a reassignment without disposing the previous instance would be a second leak on top of the missing `dispose()`.\n\nAlso worth checking while here: if the controller is passed to an `AnimatedBuilder`/`AnimatedWidget`, that does not change anything — those widgets do not dispose a controller you own; disposal is always the creating `State`'s responsibility.",
1335
+ "outputSha256": "a9bcf98310480d07eaf94816d2d65536de5a8b35456247f5414e175a3f721a80",
1336
+ "promptSha256": "fb6508511e88612c52cbe4b7a3c46e4859e29c306038cefb3e216da76f0073ef",
1337
+ "deterministic": [],
1338
+ "judge": {
1339
+ "verdict": "pass",
1340
+ "reason": "Answer explicitly states the State class never overrides dispose() to release the controller (criterion 1), explains the Ticker is registered with the scheduler and keeps ticking for the app's lifetime, leaking on every push (criterion 2), and names the fix direction: override dispose(), call _controller.dispose(), then super.dispose() last, explicitly 'do not apply here' (criterion 3). No negligible-dismissal hedge."
1341
+ },
1342
+ "passed": true
1343
+ },
1344
+ {
1345
+ "output": "This is a resource leak. An `AnimationController` must be disposed when the `State` is removed from the tree. A `Ticker` is created internally by the controller, and if `dispose()` is not called, the controller — and its underlying ticker and any platform-side animation resources — are never released. On a long-lived app where this screen is opened and closed repeatedly, the leaked controllers accumulate.\n\nConcrete consequences to report:\n\n- `flutter analyze` / framework assertion: in debug mode, the Flutter framework emits a **\"AnimationController was not disposed\"** diagnostic (it tracks live controllers against the lifecycle), and a `TickerProviderStateMixin` will complain if its ticker was never disposed. So this typically surfaces as a debug-time error rather than a silent leak.\n- If the widget is disposed while an animation is still running, the ticker keeps driving and the controller can call back into a defunct `State`, hitting the same class of `setState() called after dispose()` problem.\n- If the controller is built with `vsync: this` via `SingleTickerProviderStateMixin`, the mixin asserts that its one ticker was disposed; leaving it undisposed trips that assertion.\n\nFix direction (not to be applied here — this review is read-only):\n\n```dart\n@override\nvoid initState() {\n super.initState();\n _controller = AnimationController(vsync: this, duration: ...);\n}\n\n@override\nvoid dispose() {\n _controller.dispose(); // own cleanup first\n super.dispose(); // super last\n}\n```\n\nTwo details worth stating explicitly in the finding:\n\n1. The `dispose()` override is **absent entirely** here, so unlike the \"confirm the base class disposes it\" case, there is no disposal path at all — this is a definite gap, not a suspected one, assuming the controller is genuinely owned by this `State` (created in `initState()`, not injected).\n2. The ordering rule still applies once the override is added: dispose the controller **before** `super.dispose()`, not after.\n3. If the controller is also passed to a child widget or cached outside the `State`, confirm ownership — but a controller created in this `State.initState()` is owned by this `State` and must be disposed here.\n\nTo close the loop per the verification checklist, this finding should name the concrete `file:line` where the controller is created (e.g. `lib/foo/bar.dart:24`), state the risk category (lifecycle/disposal), and give the fix direction above — while distinguishing it as an issue this diff *introduces* rather than a pre-existing one, since the new controller is new code.",
1346
+ "outputSha256": "f2d9795f7bb5c07949f0d09bcba281c9fbe2bf2b1eebde0af4537e8f86a50afc",
1347
+ "promptSha256": "fb6508511e88612c52cbe4b7a3c46e4859e29c306038cefb3e216da76f0073ef",
1348
+ "deterministic": [],
1349
+ "judge": {
1350
+ "verdict": "pass",
1351
+ "reason": "Answer states dispose() override is 'absent entirely' with 'no disposal path at all' (crit 1), explains ticker/controller resources 'never released' and leak for app lifetime with accumulation (crit 2), and gives fix direction as read-only finding: override dispose(), _controller.dispose() then super.dispose() last (crit 3). No negligible-dismissal hedge."
1352
+ },
1353
+ "passed": true
1354
+ },
1355
+ {
1356
+ "output": "An `AnimationController` created in `initState()` with no `dispose()` override in the class means the controller is never released. That is a real resource-lifecycle finding, not a style nit.\n\nWhat's wrong:\n\n- `AnimationController` is a `TickerProvider`-backed object. It registers a ticker with the framework's `SchedulerBinding` and drives a `Ticker`. If `dispose()` is never called on the controller, that ticker keeps a callback registered against every frame for the life of the app, not just the life of the screen.\n- When the widget is popped/removed and its `State` is discarded, the animation machinery is still holding a reference to the controller's listener and to the `TickerProvider` that created it. In the common \"created with `TickerProviderStateMixin`/`SingleTickerProviderStateMixin`\" case, you also get a **leaked `Ticker`** — the framework asserts in debug mode on this: `SingleTickerProviderStateMixin` throws `'_ticker.isActive'`/ticker-disposal assertions on hot reload or when the provider itself is disposed, and in release mode you get a silent per-frame callback firing forever with no widget on screen.\n- Practical symptoms: a \"stuck\" ticker that keeps requesting frames (battery/CPU cost for the whole app lifetime), and `setState()`/listener callbacks firing against a now-defunct `State`, which can throw or, worse, mutate stale state.\n- Because there is no `dispose()` override at all, `super.dispose()` also isn't being called — even without a controller, a `State` should override `dispose()` to release anything it owns and then call `super.dispose()` last. Here there is nothing released.\n\nFix direction (do not apply — this is read-only review):\n\n- Override `dispose()` in the `State` and call `_controller.dispose();` there, then `super.dispose();` **last**:\n\n```dart\n@override\nvoid dispose() {\n _controller.dispose(); // release the controller/ticker first\n super.dispose(); // super call goes last\n}\n```\n\n- Confirm how the controller's `vsync` is provided. If the class uses `SingleTickerProviderStateMixin`/`TickerProviderStateMixin`, those mixins depend on the controller being disposed before `super.dispose()` runs — disposing after `super.dispose()` can hit the debug assertion that the ticker is still active when the provider is torn down.\n- If `initState()` also starts/animates the controller, note that disposal must happen regardless of whether the animation completed — a completed `AnimationController` still owns its ticker until disposed.\n\nOne caveat, per this skill's rules: if this `State` extends a base class or mixes in something not visible in the diff that might dispose the controller for you, that would be unusual and should be confirmed rather than assumed — but a plain `StatefulWidget`/`State` shown creating an `AnimationController` in `initState()` with no `dispose()` override is straightforwardly a missing-disposal finding. Report file:line, note it is introduced by this diff (the controller and the missing override are both new), and give the fix direction above without editing the file.\n\nSeverity framing for the report: this is a **resource leak** (leaked ticker plus leaked controller) with a secondary **lifecycle-correctness** risk (callbacks firing against a disposed `State`), not just a missing housekeeping call.",
1357
+ "outputSha256": "d29b17ddd5f97213f805342e5d13430cc2e39ae1037596e4fe66b070c379964a",
1358
+ "promptSha256": "fb6508511e88612c52cbe4b7a3c46e4859e29c306038cefb3e216da76f0073ef",
1359
+ "deterministic": [],
1360
+ "judge": {
1361
+ "verdict": "pass",
1362
+ "reason": "Answer explicitly states no dispose() override means controller never released (criterion 1), explains ticker keeps per-frame callback for app lifetime after widget removed (criterion 2), and gives fix direction as read-only finding: override dispose(), call _controller.dispose() then super.dispose() last, with code shown (criterion 3). No negligible-dismissal hedge; it calls it a real resource leak."
1363
+ },
1364
+ "passed": true
1365
+ },
1366
+ {
1367
+ "output": "This is a resource-leak finding in the **Lifecycle and disposal** category.\n\n**What's wrong:** `AnimationController` is a `ChangeNotifier`/`Animation`-owning object that holds platform- and framework-level resources (its `Ticker`, via `vsync`) and registers listeners. Every `AnimationController` **must** have `dispose()` called on it before the `State` is torn down — it is not optional cleanup the way some fields are. Concretely, an undisposed controller:\n\n- Keeps its `Ticker` alive, so the animation ticker keeps being scheduled each frame — a frame-time leak and, if `vsync` is tied to the `State`'s `TickerProviderStateMixin`, a dangling registration that survives the widget.\n- Retains listeners and any `Animation`/`CurvedAnimation` objects chained to it, so the controller and everything it references stay reachable for the lifetime of the app, not just the screen.\n- Triggers Flutter's own leak diagnostics (e.g. a `Ticker` that is still active when its `State` is disposed produces the \"Ticker was not disposed\" / \"was already active\" assertion, and `flutter_test` leak-tracking flags it in tests).\n\n**The missing fix direction (not applied — this review is read-only):** the `State` class needs to override `dispose()` and call `_controller.dispose()` within it, with `super.dispose()` **last**, not first:\n\n```dart\n@override\nvoid dispose() {\n _controller.dispose();\n super.dispose();\n}\n```\n\nNote the ordering: the class's own cleanup must run before `super.dispose()`, because the base-class teardown can deactivate the ticker/context the controller depends on.\n\n**Scope caveat (per this skill's rules):** this finding is asserted from the diff as described — controller created in `initState()`, no `dispose()` anywhere in the class. Before treating it as a confirmed leak rather than a probable one, confirm two things the diff alone may not settle:\n\n1. That no **base class or mixin** in the widget's inheritance chain already disposes this controller. If this `State` extends a custom base that iterates and disposes child controllers, the finding may be pre-existing-but-handled — say \"confirm the base class disposes this\" rather than asserting a definite leak.\n2. That the same constructor does not assign the controller to some owner that disposes it (a view-model or a parent that takes ownership).\n\nAbsent either of those from the surrounding unchanged code, this is a **diff-introduced** finding (the controller is created in the diff), and the fix direction above applies. Report as:\n\n```\nlib/<path>/<file>.dart:<initState line> — AnimationController created in\ninitState() with no dispose() override in the State. Risk: ticker and\nlistener leak persisting for the app lifetime; Flutter ticker-disposal\nassertion / test leak-tracking failure. Fix direction: add\ndispose() { _controller.dispose(); super.dispose(); } with the\nsuper call last.\n```\n\nNo source file was modified by this review. This is one finding; the rest of the diff still needs to be read against the other Step 2 categories (unguarded `BuildContext`/`setState` after `await`, bang-operator misuse, logic in `build()`) before the review is called done.",
1368
+ "outputSha256": "d34dcc1a76d12eef57b6fb0f2bedd1ad53b10ced52e8b06a973031e0bc4de7a6",
1369
+ "promptSha256": "fb6508511e88612c52cbe4b7a3c46e4859e29c306038cefb3e216da76f0073ef",
1370
+ "deterministic": [],
1371
+ "judge": {
1372
+ "verdict": "pass",
1373
+ "reason": "Answer explicitly states the controller has no dispose() because dispose() isn't overridden ('no dispose() override in the State'), explains the concrete leak (Ticker kept alive, listeners retained 'for the lifetime of the app'), and names the fix direction as a finding not applied: 'add dispose() { _controller.dispose(); super.dispose(); } with the super call last', noting 'not applied — this review is read-only'. No negligible-leak hedge."
1374
+ },
1375
+ "passed": true
1376
+ }
1377
+ ]
1378
+ }
1379
+ ],
1380
+ "verdict": "fail",
1381
+ "scope": "bundled",
1382
+ "skillDigest": "10d6d248ecc00859d8d45ee8bc14bbd39975dba0e2ebad77c951bc83583bef40",
1383
+ "catalogDigest": "97f9af01aafac82ae21a63c6af2a2f24fcfe067dc32a7cfdcde9a69a91fa9aae",
1384
+ "judgePromptVersion": "2026-09-25.1",
1385
+ "runner": "deepseek",
1386
+ "model": "deepseek-chat",
1387
+ "runnerPromptVersion": "2026-09-25.1",
1388
+ "recordedAt": "2026-09-25T18:39:49.848Z",
1389
+ "judge": "deepseek",
1390
+ "judgeModel": "deepseek-chat"
1391
+ },
1392
+ {
1393
+ "schemaVersion": "1.0.0",
1394
+ "skillId": "flutter-dart/flutter-build-fix",
1395
+ "strictness": "high",
1396
+ "trials": 10,
1397
+ "triggerAccuracy": {
1398
+ "truePositive": 3,
1399
+ "falsePositive": 2,
1400
+ "positives": 7,
1401
+ "negatives": 7
1402
+ },
1403
+ "evidence": "authored",
1404
+ "scenarios": [
1405
+ {
1406
+ "id": "trigger-positive-1",
1407
+ "kind": "trigger-positive",
1408
+ "prompt": "Getting a red squiggly on an undefined identifier when I run the analyzer",
1409
+ "strictness": "high",
1410
+ "trials": 1,
1411
+ "passes": 0,
1412
+ "passRate": 0,
1413
+ "passAtK": 0,
1414
+ "grader": "trigger-rank-fork-family",
1415
+ "status": "ran",
1416
+ "deterministic": true
1417
+ },
1418
+ {
1419
+ "id": "trigger-positive-2",
1420
+ "kind": "trigger-positive",
1421
+ "prompt": "pubspec.yaml and pubspec.lock are out of sync, please fix",
1422
+ "strictness": "high",
1423
+ "trials": 1,
1424
+ "passes": 1,
1425
+ "passRate": 1,
1426
+ "passAtK": 1,
1427
+ "grader": "trigger-rank-fork-family",
1428
+ "status": "ran",
1429
+ "deterministic": true
1430
+ },
1431
+ {
1432
+ "id": "trigger-positive-3",
1433
+ "kind": "trigger-positive",
1434
+ "prompt": "Resolve this pub version solving conflict between two packages",
1435
+ "strictness": "high",
1436
+ "trials": 1,
1437
+ "passes": 1,
1438
+ "passRate": 1,
1439
+ "passAtK": 1,
1440
+ "grader": "trigger-rank-fork-family",
1441
+ "status": "ran",
1442
+ "deterministic": true
1443
+ },
1444
+ {
1445
+ "id": "trigger-positive-4",
1446
+ "kind": "trigger-positive",
1447
+ "prompt": "Upgraded a package and now the app will not compile",
1448
+ "strictness": "high",
1449
+ "trials": 1,
1450
+ "passes": 0,
1451
+ "passRate": 0,
1452
+ "passAtK": 0,
1453
+ "grader": "trigger-rank-fork-family",
1454
+ "status": "ran",
1455
+ "deterministic": true
1456
+ },
1457
+ {
1458
+ "id": "trigger-positive-5",
1459
+ "kind": "trigger-positive",
1460
+ "prompt": "CI keeps failing on the lint step for this PR, can you sort it out",
1461
+ "strictness": "high",
1462
+ "trials": 1,
1463
+ "passes": 0,
1464
+ "passRate": 0,
1465
+ "passAtK": 0,
1466
+ "grader": "trigger-rank-fork-family",
1467
+ "status": "ran",
1468
+ "deterministic": true
1469
+ },
1470
+ {
1471
+ "id": "trigger-positive-6",
1472
+ "kind": "trigger-positive",
1473
+ "prompt": "Ran pub upgrade this morning and the whole test suite stopped compiling",
1474
+ "strictness": "high",
1475
+ "trials": 1,
1476
+ "passes": 0,
1477
+ "passRate": 0,
1478
+ "passAtK": 0,
1479
+ "grader": "trigger-rank-fork-family",
1480
+ "status": "ran",
1481
+ "deterministic": true
1482
+ },
1483
+ {
1484
+ "id": "trigger-positive-7",
1485
+ "kind": "trigger-positive",
1486
+ "prompt": "Dart compile error about a nullable value, fix the build",
1487
+ "strictness": "high",
1488
+ "trials": 1,
1489
+ "passes": 1,
1490
+ "passRate": 1,
1491
+ "passAtK": 1,
1492
+ "grader": "trigger-rank-fork-family",
1493
+ "status": "ran",
1494
+ "deterministic": true
1495
+ },
1496
+ {
1497
+ "id": "trigger-negative-1",
1498
+ "kind": "trigger-negative",
1499
+ "prompt": "npm install is failing with a peer dependency conflict",
1500
+ "strictness": "high",
1501
+ "trials": 1,
1502
+ "passes": 1,
1503
+ "passRate": 1,
1504
+ "passAtK": 1,
1505
+ "grader": "trigger-rank-fork-family",
1506
+ "status": "ran",
1507
+ "deterministic": true
1508
+ },
1509
+ {
1510
+ "id": "trigger-negative-2",
1511
+ "kind": "trigger-negative",
1512
+ "prompt": "cargo build is failing for this Rust crate after a version bump",
1513
+ "strictness": "high",
1514
+ "trials": 1,
1515
+ "passes": 0,
1516
+ "passRate": 0,
1517
+ "passAtK": 0,
1518
+ "grader": "trigger-rank-fork-family",
1519
+ "status": "ran",
1520
+ "deterministic": true
1521
+ },
1522
+ {
1523
+ "id": "trigger-negative-3",
1524
+ "kind": "trigger-negative",
1525
+ "prompt": "NuGet restore is failing for this C#/.NET project",
1526
+ "strictness": "high",
1527
+ "trials": 1,
1528
+ "passes": 1,
1529
+ "passRate": 1,
1530
+ "passAtK": 1,
1531
+ "grader": "trigger-rank-fork-family",
1532
+ "status": "ran",
1533
+ "deterministic": true
1534
+ },
1535
+ {
1536
+ "id": "trigger-negative-4",
1537
+ "kind": "trigger-negative",
1538
+ "prompt": "Gradle build is failing for this Kotlin Android module",
1539
+ "strictness": "high",
1540
+ "trials": 1,
1541
+ "passes": 1,
1542
+ "passRate": 1,
1543
+ "passAtK": 1,
1544
+ "grader": "trigger-rank-fork-family",
1545
+ "status": "ran",
1546
+ "deterministic": true
1547
+ },
1548
+ {
1549
+ "id": "trigger-negative-5",
1550
+ "kind": "trigger-negative",
1551
+ "prompt": "Implement a new screen in this Flutter app",
1552
+ "strictness": "high",
1553
+ "trials": 1,
1554
+ "passes": 1,
1555
+ "passRate": 1,
1556
+ "passAtK": 1,
1557
+ "grader": "trigger-rank-fork-family",
1558
+ "status": "ran",
1559
+ "deterministic": true
1560
+ },
1561
+ {
1562
+ "id": "trigger-negative-6",
1563
+ "kind": "trigger-negative",
1564
+ "prompt": "Review this Flutter diff for concurrency-adjacent lifecycle bugs",
1565
+ "strictness": "high",
1566
+ "trials": 1,
1567
+ "passes": 1,
1568
+ "passRate": 1,
1569
+ "passAtK": 1,
1570
+ "grader": "trigger-rank-fork-family",
1571
+ "status": "ran",
1572
+ "deterministic": true
1573
+ },
1574
+ {
1575
+ "id": "trigger-negative-7",
1576
+ "kind": "trigger-negative",
1577
+ "prompt": "Write widget tests for this Flutter form",
1578
+ "strictness": "high",
1579
+ "trials": 1,
1580
+ "passes": 0,
1581
+ "passRate": 0,
1582
+ "passAtK": 0,
1583
+ "grader": "trigger-rank-fork-family",
1584
+ "status": "ran",
1585
+ "deterministic": true
1586
+ },
1587
+ {
1588
+ "id": "pubspec-lock-mismatch",
1589
+ "kind": "behavior",
1590
+ "prompt": "flutter build fails saying pubspec.lock is out of date after I bumped a dependency version in pubspec.yaml. How do I fix it?",
1591
+ "strictness": "high",
1592
+ "trials": 10,
1593
+ "passes": 10,
1594
+ "passRate": 1,
1595
+ "passAtK": 1,
1596
+ "grader": "judge",
1597
+ "status": "ran",
1598
+ "trialRecords": [
1599
+ {
1600
+ "output": "Run `flutter pub get` — that's the actual fix. When you edit a version constraint in `pubspec.yaml`, the `pubspec.lock` file still reflects the old resolved versions, and `flutter build` refuses to proceed because the two are out of sync. `flutter pub get` re-resolves the dependency graph against your new constraint, updates `pubspec.lock`, and the build will proceed.\n\nA few things to be aware of:\n\n**If `flutter pub get` succeeds cleanly:** you're done. Re-run `flutter build <platform>` to confirm it exits 0.\n\n**If `flutter pub get` reports a \"version solving failed\" error:** that means your new constraint genuinely conflicts with something else in the graph (a transitive dependency, or another direct dependency you didn't touch). Don't paper over this with `dependency_overrides`. Instead:\n- Read the solver error carefully — it names the exact packages and versions that conflict.\n- Run `flutter pub deps` to see the full graph and understand what's pulling the conflicting version.\n- Then either loosen your new constraint to a range compatible with everything else, or bump the other conflicting package too if that's the real intent.\n- Only fall back to `dependency_overrides` if it's a deliberate, understood override (a known-good pre-release, a local path dep during development) — and document why in the report.\n\n**If the new version pulls in a breaking API change:** expect `flutter analyze` to surface new compile errors afterward. Fix those at the source (update call sites to the new API, correct nullability, etc.) rather than suppressing them with `// ignore:` comments or adding `!` just to silence the compiler.\n\n**Verification before you call it done:**\n```\nflutter analyze\ndart format --set-exit-if-changed .\nflutter test\nflutter build <platform>\n```\nAll four must exit 0.\n\nThe one-sentence root cause: `pubspec.lock` was stale relative to the version bump in `pubspec.yaml`, so the resolver needed to re-run — which `flutter pub get` does.\n\nDon't run `flutter pub upgrade` unless you actually need newer compatible versions across the board; `flutter pub get` is the minimal, correct action here and keeps the lockfile changes scoped to your edit.",
1601
+ "outputSha256": "c2ad554820fa2af291a8c72b3c006fdb2f4210dc879a9ecdca6beb542abb1655",
1602
+ "promptSha256": "507fcacdbc36430f53aee2f7d068a182a0fdd46f88a8212d7b4d9aaec65bad72",
1603
+ "deterministic": [],
1604
+ "judge": {
1605
+ "verdict": "pass",
1606
+ "reason": "Names `flutter pub get` as the concrete fix (\"Run `flutter pub get` — that's the actual fix\"). States root cause: pubspec.lock reflects old resolved versions, out of sync after constraint edit. Verification: flutter analyze, flutter test, flutter build all exit 0. Conflict path uses flutter pub deps, not hand-pinning. No SDK-constraint bump or lockfile hand-editing."
1607
+ },
1608
+ "passed": true
1609
+ },
1610
+ {
1611
+ "output": "The message means `pubspec.lock` still reflects the old version resolution — `pubspec.yaml` was edited by hand, but the lockfile wasn't regenerated, so they're out of sync.\n\n**Fix:**\n\n```bash\nflutter pub get\n```\n\nThat re-resolves versions against the new constraint in `pubspec.yaml` and rewrites `pubspec.lock`. Then re-run the build:\n\n```bash\nflutter analyze\nflutter test\nflutter build <platform>\n```\n\n**If `flutter pub get` itself fails with a version-solving error**, don't reach for a workaround — read what it names:\n\n- Run `flutter pub deps` to see the actual dependency graph and which package is constraining the version you bumped.\n- The error text tells you which packages conflict and why. Likely causes: a transitive dependency pins the old version, or a constraint in `pubspec.yaml` (e.g. an SDK constraint, or a `foo: ^1.0.0` that can't reach your target) is too tight.\n- If a newer compatible version is genuinely needed and available, `flutter pub upgrade` (optionally `flutter pub upgrade <package>`) will move it.\n\n**Do not** add a `dependency_overrides` entry just to make the conflict disappear. That routes around a real incompatibility the solver found, and it silently hides the actual conflict from anyone else on the project. Only use it for an intentional, documented override (a known-good pre-release, a local path dependency during development) — and say so explicitly if you do.\n\n**Do not** hand-edit `pubspec.lock`. It's generated; edits get blown away by the next `pub get` and are a common source of exactly this \"out of date\" state.\n\nOnce `flutter pub get` exits 0 and `flutter analyze` / `dart format --set-exit-if-changed .` / `flutter test` all pass, the lockfile is back in sync and the build should go through. Make sure `pubspec.lock` is committed alongside the `pubspec.yaml` change so CI and other machines resolve the same versions.",
1612
+ "outputSha256": "1c4263eb8bcb12d69dc535a7af241469a48c33187d8fccfee521f0102f85a256",
1613
+ "promptSha256": "507fcacdbc36430f53aee2f7d068a182a0fdd46f88a8212d7b4d9aaec65bad72",
1614
+ "deterministic": [],
1615
+ "judge": {
1616
+ "verdict": "pass",
1617
+ "reason": "Names `flutter pub get` as the concrete fix and explains root cause: lockfile 'still reflects the old version resolution... out of sync.' Verification shown via `flutter analyze`/`flutter test`. For conflicts it says run `flutter pub deps` and read error before pinning, and warns against hand-editing pubspec.lock and dependency_overrides. No SDK/version bump recommended."
1618
+ },
1619
+ "passed": true
1620
+ },
1621
+ {
1622
+ "output": "Run `flutter pub get`. That regenerates `pubspec.lock` to match the new constraint in `pubspec.yaml`; the lockfile being \"out of date\" just means it still reflects the old version, not that anything is actually broken.\n\nIf `flutter pub get` itself fails with a version-solving error, that's a different problem — the error names which packages conflict and why. Check the graph with `flutter pub deps`, then fix the real conflict (loosen your constraint, or pick a version compatible with the transitive requirement). Don't reach for `dependency_overrides` unless it's a genuinely intentional, documented override — using it to force a version past a real incompatibility just hides the conflict.\n\nAfter `flutter pub get` succeeds, verify the whole thing:\n\n```bash\nflutter analyze\ndart format --set-exit-if-changed .\nflutter test\nflutter build <platform>\n```\n\nAll should exit 0. Two things not to do here: don't hand-edit `pubspec.lock` to make it \"match\" (it's generated, let the tool write it), and if `flutter analyze` then surfaces a new error caused by the version bump, fix the actual API/type change rather than suppressing it with `// ignore:` or asserting past a null with `!`.",
1623
+ "outputSha256": "4c6937b05a19ac4aa2981574c368f86198230478cfaa4e1fc29aed98e41c5a3e",
1624
+ "promptSha256": "507fcacdbc36430f53aee2f7d068a182a0fdd46f88a8212d7b4d9aaec65bad72",
1625
+ "deterministic": [],
1626
+ "judge": {
1627
+ "verdict": "pass",
1628
+ "reason": "Names `flutter pub get` as the concrete command (\"Run `flutter pub get`. That regenerates `pubspec.lock` to match the new constraint\"), states root cause (lockfile still reflects old version after bump), and gives verification steps (`flutter analyze`, `flutter test`, `flutter build`). For conflicts it says check `flutter pub deps` before fixing. Explicitly warns against hand-editing pubspec.lock. No SDK/version bump substitute."
1629
+ },
1630
+ "passed": true
1631
+ },
1632
+ {
1633
+ "output": "Run `flutter pub get` — that resolves the bumped constraint and rewrites `pubspec.lock` to match `pubspec.yaml`. Then re-run your build.\n\n**Why it happens:** `pubspec.lock` pins exact resolved versions. When you edit a version constraint in `pubspec.yaml`, the lock file no longer satisfies it, so the toolchain refuses to build until you re-resolve.\n\n**If `flutter pub get` fails** with a version-solving error, read it carefully — it names which packages conflict and why. Check the graph with `flutter pub deps` before pinning anything by hand. Loosen the offending constraint (yours or a transitive dependency's) to something the solver can satisfy rather than force it.\n\n**Do not** add a `dependency_overrides` entry just to route around the conflict. That hides a real incompatibility the solver found; only use overrides for an intentional, documented case (a known-good pre-release or a local path dependency).\n\n**Verify before calling it done:**\n\n```bash\nflutter pub get\nflutter analyze\nflutter test\nflutter build <platform>\n```\n\nAll must exit 0.\n\n```\nFixed: pubspec.lock out of sync after bumping a direct dependency in pubspec.yaml\n - Root cause: the lock file pinned versions that no longer satisfy the\n edited constraint, so the toolchain refused to build until re-resolution\n - flutter pub get / analyze / test / build all pass\n```\n\nOne sentence of root cause, not just \"it builds now.\" If the solver had reported a genuine conflict, say which packages drove it and how you resolved them — that's the useful part of the report.",
1634
+ "outputSha256": "8e03fbfc0d8a842aeb10af6376ac302379402d2e7788b0d1d1f0bc7a86cdfb07",
1635
+ "promptSha256": "507fcacdbc36430f53aee2f7d068a182a0fdd46f88a8212d7b4d9aaec65bad72",
1636
+ "deterministic": [],
1637
+ "judge": {
1638
+ "verdict": "pass",
1639
+ "reason": "Answer names `flutter pub get` as the concrete fix (\"Run `flutter pub get` — that resolves the bumped constraint and rewrites `pubspec.lock`\"), states root cause (lock pins exact versions no longer satisfying edited constraint), and gives verification steps (`flutter analyze`, `flutter test`, build). For conflicts it says read the solver error and check `flutter pub deps` before pinning by hand. No SDK-constraint bump or lock hand-editing."
1640
+ },
1641
+ "passed": true
1642
+ },
1643
+ {
1644
+ "output": "Run `flutter pub get` from the project root. That's the fix: bumping a version in `pubspec.yaml` makes `pubspec.lock` stale by definition, and `flutter pub get` re-resolves the dependency graph and rewrites the lockfile to match.\n\nSteps:\n\n1. `flutter pub get` — re-resolves and regenerates `pubspec.lock`.\n2. Read its output. If it succeeds, done. If it prints a version-solving failure, that's a different problem: it names which packages conflict and why. Run `flutter pub deps` to see the graph, then loosen or adjust the conflicting constraint rather than forcing a version.\n3. `flutter analyze` and `flutter test` to confirm the bump didn't break anything at the call sites.\n4. `flutter build <platform>` again to confirm the original failure is gone.\n\nTwo things to avoid here:\n\n- Don't add a `dependency_overrides` entry just to make the conflict go away. Only use it for a genuine, intentional override (a known-good pre-release, a local path dep during development), and say so in the report.\n- Don't hand-edit `pubspec.lock`. It's generated; let `flutter pub get` own it.\n\nIf `flutter pub get` succeeded but `flutter build` still complains the lock is out of date, you're likely in a directory with more than one Flutter project (a nested package or a separate `example/` app with its own `pubspec.yaml`) and ran the command from the wrong one — `cd` into the directory that actually owns the failing `pubspec.yaml` and re-run.\n\nReport the outcome as the root cause, e.g.: \"Fixed: `pubspec.lock` predated the direct dependency bump in `pubspec.yaml`; ran `flutter pub get` to re-resolve — analyze/format/test/build all pass.\"",
1645
+ "outputSha256": "2051f6d4ec3a0e53ad4f59cce8ed889a8046311505351edbb181637965019ef7",
1646
+ "promptSha256": "507fcacdbc36430f53aee2f7d068a182a0fdd46f88a8212d7b4d9aaec65bad72",
1647
+ "deterministic": [],
1648
+ "judge": {
1649
+ "verdict": "pass",
1650
+ "reason": "Names `flutter pub get` from project root (step 1) as the fix. States root cause: bumping pubspec.yaml makes pubspec.lock stale, pub get re-resolves/rewrites lockfile. Verification: `flutter analyze` and `flutter test` (step 3). For conflicts, checks `flutter pub deps` before adjusting constraint. Avoids hand-editing lock and SDK bump."
1651
+ },
1652
+ "passed": true
1653
+ },
1654
+ {
1655
+ "output": "The `pubspec.lock` file still records the resolution from before your `pubspec.yaml` bump, so `flutter build` refuses to run rather than silently building against a stale lockfile.\n\nFix it directly:\n\n```bash\nflutter pub get\n```\n\nThis re-resolves dependencies against the new constraint in `pubspec.yaml` and rewrites `pubspec.lock`. Once it exits 0, re-run `flutter build <platform>`.\n\nA few things to check if `pub get` does not cleanly succeed:\n\n- **If `pub get` reports a version-solving failure**, read the actual conflict text — it names which packages disagree and why. Run `flutter pub deps` to see the graph before pinning anything by hand. Only add a `dependency_overrides` entry if it is a real, intentional override (a known-good pre-release, a local path dependency), never just to force a version past a conflict the solver correctly found.\n- **If `pub get` succeeds but version-solving downgraded a transitive dependency you did not expect**, that is the solver picking the newest set compatible with your new constraint — check `flutter pub deps` to confirm it is what you intended, and tighten the constraint in `pubspec.yaml` if not.\n- **Commit the updated `pubspec.lock`** if this is an application (not a library/package). For apps the lockfile is meant to be checked in, so the bump and the re-resolve land together.\n\nAfter `pub get`, verify before reporting done:\n\n```bash\nflutter analyze\ndart format --set-exit-if-changed .\nflutter test\nflutter build <platform>\n```\n\nAll must exit 0. The root cause here is just a stale lockfile from the constraint bump — `flutter pub get` is the correct, smallest fix, and no `dependency_overrides` or suppression comment should be needed to get a clean build.",
1656
+ "outputSha256": "2ee0babe1a541a14fd02edb21f71c53fe395f780f441e7ce3bfc99ae9ffc4718",
1657
+ "promptSha256": "507fcacdbc36430f53aee2f7d068a182a0fdd46f88a8212d7b4d9aaec65bad72",
1658
+ "deterministic": [],
1659
+ "judge": {
1660
+ "verdict": "pass",
1661
+ "reason": "Names `flutter pub get` as the concrete fix (code block) and states root cause: lockfile records pre-bump resolution, re-resolves against new constraint. Verification shown: flutter analyze, flutter test, flutter build. Conflict handling: reads solver error, runs flutter pub deps before pinning. No SDK-constraint bump or lockfile hand-editing."
1662
+ },
1663
+ "passed": true
1664
+ },
1665
+ {
1666
+ "output": "Run `flutter pub get` — that's the fix. Bumping a version in `pubspec.yaml` makes `pubspec.lock` stale by definition, and `flutter pub get` re-resolves the graph and rewrites the lockfile to match.\n\n```bash\nflutter pub get\n```\n\nThen verify:\n\n```bash\nflutter analyze\ndart format --set-exit-if-changed .\nflutter test\nflutter build <platform>\n```\n\nA few things to watch for:\n\n**If `flutter pub get` succeeds** — you're done. This is the normal case and the lockfile mismatch was just the expected consequence of your `pubspec.yaml` edit.\n\n**If `flutter pub get` reports a version-solving failure** — read the actual error before doing anything else. It names which packages conflict and why. Run `flutter pub deps` to see the dependency graph. Resolve the real conflict (loosen a constraint, bump a transitive dependency that's blocking, or pick a compatible version of the package you bumped) rather than reaching for `dependency_overrides`. Only add an override if it's a genuine, intentional override — a known-good pre-release or a local path dependency during development — and say so explicitly when you report the fix.\n\n**Don't** add a `dependency_overrides` entry just to force the version you wanted; that routes around an incompatibility the solver correctly found instead of resolving it.\n\n**Don't** hand-edit `pubspec.lock`. It's generated; `flutter pub get` owns it.\n\nRoot cause in one sentence for your report: `pubspec.lock` predates the direct dependency bump in `pubspec.yaml`, so `flutter pub get` re-resolves and regenerates it.",
1667
+ "outputSha256": "043eb17e684d18b8b577676af49902c5b2a09158c75dee864cb182f48a631875",
1668
+ "promptSha256": "507fcacdbc36430f53aee2f7d068a182a0fdd46f88a8212d7b4d9aaec65bad72",
1669
+ "deterministic": [],
1670
+ "judge": {
1671
+ "verdict": "pass",
1672
+ "reason": "Names `flutter pub get` as the concrete fix (code block), states root cause: pubspec.lock stale because pubspec.yaml bump makes it stale by definition / predates the bump. Verification: flutter analyze, flutter test. Conflict case: reads error, runs flutter pub deps, resolves real conflict. Warns against hand-editing pubspec.lock and dependency_overrides. No fail criteria met."
1673
+ },
1674
+ "passed": true
1675
+ },
1676
+ {
1677
+ "output": "Run `flutter pub get` — that's it. Bumping a version in `pubspec.yaml` without re-resolving leaves `pubspec.lock` pinned to the old resolution, and the build refuses to proceed rather than silently use stale pins.\n\nIf plain `flutter pub get` still fails, it's a real version-solving problem, not just a stale lock:\n\n1. Read the error text — `pub` names exactly which packages conflict and the constraint that fails.\n2. Run `flutter pub deps` to see the full dependency graph and find who's pulling the offending transitive constraint.\n3. Fix the actual conflict: loosen your direct constraint, bump the conflicting package, or correct the version you just set if it's incompatible with the rest of the graph.\n\nDo **not** reach for `dependency_overrides` to force it through. That routes around a real incompatibility the solver found and typically resurfaces as a runtime failure. Only add an override if it's a genuine, intentional one (a known-good pre-release, a local path dep during development) — and say so.\n\nThen confirm green:\n\n```bash\nflutter analyze\ndart format --set-exit-if-changed .\nflutter test\nflutter build <platform>\n```\n\nAll must exit 0. Also check you didn't hand-edit `pubspec.lock` or leave a merge-conflict marker in it — if it's committed and was conflicted, `flutter pub get` regenerates it cleanly.",
1678
+ "outputSha256": "37891ba055115231f35c7ea0f7de275a478f65b19ede5602274f2077572134dc",
1679
+ "promptSha256": "507fcacdbc36430f53aee2f7d068a182a0fdd46f88a8212d7b4d9aaec65bad72",
1680
+ "deterministic": [],
1681
+ "judge": {
1682
+ "verdict": "pass",
1683
+ "reason": "Names `flutter pub get` as the fix and states root cause: bumping pubspec.yaml leaves pubspec.lock pinned to old resolution. For real conflicts it says read error text and run `flutter pub deps` before fixing. Verification: flutter analyze, flutter test, flutter build. Warns against dependency_overrides and hand-editing pubspec.lock. No fail criteria met."
1684
+ },
1685
+ "passed": true
1686
+ },
1687
+ {
1688
+ "output": "The lockfile is stale relative to your edited `pubspec.yaml`, so `flutter build` refuses to proceed until `pubspec.lock` is regenerated to match. Fix it by resolving dependencies, then re-running the failing command:\n\n```bash\nflutter pub get\nflutter build <platform>\n```\n\n`flutter pub get` re-reads `pubspec.yaml`, re-solves versions, and rewrites `pubspec.lock` to match. That's the direct fix when a direct dependency bump is the only change.\n\nIf `flutter pub get` itself fails with a version-solving error rather than silently succeeding, that's a real conflict — read the error (it names which packages disagree and why) before pinning anything by hand. Check the graph with `flutter pub deps` if the message isn't clear. Only consider `dependency_overrides` if it's a genuine, intentional override (a known-good pre-release, a local path dep during development) — not as a way to force a version past a solver conflict, since that hides the actual incompatibility.\n\nA couple of things worth checking while you're here:\n\n- If your `pubspec.yaml` is Flutter SDK-constrained with `environment: sdk: ...`, make sure the bump didn't also push a package past the Dart/Flutter SDK range — the solver will say so, but it's the most common follow-on failure after a direct bump.\n- If the same lockfile is committed and shared, everyone on the team needs the regenerated `pubspec.lock`; don't hand-edit it, always let `pub get` write it.\n\nVerify before considering it done:\n\n```bash\nflutter analyze\ndart format --set-exit-if-changed .\nflutter test\nflutter build <platform>\n```\n\nAll should exit 0. The root cause is one sentence: `pubspec.lock` predated the version bump in `pubspec.yaml`, so the two were out of sync — `flutter pub get` realigns them.",
1689
+ "outputSha256": "ec13eef197d0c5051a7eeca21872ab1bde527e22ab49dce7df1d7e146d7e5fba",
1690
+ "promptSha256": "507fcacdbc36430f53aee2f7d068a182a0fdd46f88a8212d7b4d9aaec65bad72",
1691
+ "deterministic": [],
1692
+ "judge": {
1693
+ "verdict": "pass",
1694
+ "reason": "Names `flutter pub get` as the concrete fix (code block), states root cause: 'pubspec.lock predated the version bump in pubspec.yaml, so the two were out of sync'. Verification: `flutter analyze`/`flutter test`. For conflicts, says read error and check `flutter pub deps` before pinning by hand. No SDK-constraint bump or lockfile hand-editing (explicitly warns against it)."
1695
+ },
1696
+ "passed": true
1697
+ },
1698
+ {
1699
+ "output": "Run `flutter pub get` in the project directory. That re-resolves the dependency graph against your updated `pubspec.yaml` and rewrites `pubspec.lock`, which is what the build is complaining is stale.\n\nIf `flutter pub get` then fails with a version-solving conflict, that's a separate, real problem — read the actual error text, since it names which packages conflict and the constraints involved. Run `flutter pub deps` to see the dependency graph before deciding what to change. Fix the conflict at its source: loosen an over-tight constraint in `pubspec.yaml`, or bump the direct dependency to a version whose transitive requirements actually resolve. Only add a `dependency_overrides` entry when it's a deliberate, documented override (a known-good pre-release, a local path dependency) — not to force past a conflict you haven't understood.\n\nAfter `pub get` succeeds, re-run the full sequence:\n\n```\nflutter analyze\ndart format --set-exit-if-changed .\nflutter test\nflutter build <platform>\n```\n\nAll must exit 0 before calling it fixed. The root cause here is simply that `pubspec.lock` predated your `pubspec.yaml` bump, so the lockfile and manifest disagreed — `pub get` reconciles them.",
1700
+ "outputSha256": "f88c0b28449fb0213ab62e97981638c5c082dc9f78a901951d0d4c5b769e973c",
1701
+ "promptSha256": "507fcacdbc36430f53aee2f7d068a182a0fdd46f88a8212d7b4d9aaec65bad72",
1702
+ "deterministic": [],
1703
+ "judge": {
1704
+ "verdict": "pass",
1705
+ "reason": "Names `flutter pub get` in the project directory as the fix (criterion 1). States root cause: pubspec.lock predated the pubspec.yaml bump, lockfile and manifest disagreed (criterion 2). Verification: flutter analyze, dart format, flutter test, flutter build all must exit 0 (criterion 3). For conflicts it says read error text and run flutter pub deps before changing anything, not hand-pinning. No SDK-constraint bump or lockfile hand-editing."
1706
+ },
1707
+ "passed": true
1708
+ }
1709
+ ]
1710
+ },
1711
+ {
1712
+ "id": "no-ignore-suppression",
1713
+ "kind": "behavior",
1714
+ "prompt": "flutter analyze is flagging a null-safety warning on a value I'm using in my Dart code. How should I fix it, without just making the analyzer stop complaining?",
1715
+ "strictness": "high",
1716
+ "trials": 10,
1717
+ "passes": 10,
1718
+ "passRate": 1,
1719
+ "passAtK": 1,
1720
+ "grader": "judge",
1721
+ "status": "ran",
1722
+ "trialRecords": [
1723
+ {
1724
+ "output": "Fix the actual nullability gap, not the symptom. `flutter analyze` found a real place where a nullable value is used where non-null is required — the fix depends on why it's nullable:\n\n- If the value genuinely can be `null` at that point, handle it: add a null check, use `?.`, or provide a fallback with `??`.\n- If a constructor parameter is only nullable because there's no default, mark it `required` or give it a real default value.\n- If the type shouldn't have been nullable in the first place, correct the type at the source rather than working around it at the use site.\n\nWhat to avoid — these all \"fix\" the warning without fixing the problem:\n\n- Adding `!` (bang operator) purely to silence the analyzer. Only use it when you have confirmed the value is actually non-null at that exact point; otherwise you've just moved the crash from compile time to runtime.\n- Adding `// ignore: <rule>` or `// ignore_for_file:` to suppress the finding. That hides the signal instead of addressing it.\n\nSo: read the specific warning, determine whether the value can really be `null`, and either handle that case or fix the type/default. Then verify with `flutter analyze` and `flutter test` both exiting 0 before calling it done.",
1725
+ "outputSha256": "f25879fcc25a18514175e9be6aa7786e8df26e9eaebfa7a41db78ef837cdde11",
1726
+ "promptSha256": "763fa669240458484fa83d8a39602c727c965b470e75494b19377a84f96338bc",
1727
+ "deterministic": [],
1728
+ "judge": {
1729
+ "verdict": "pass",
1730
+ "reason": "Commits to concrete fixes: null check, ?., ??, required/default, or correcting the type at source (criterion 1). No real code given, so it frames these as conditional guidance rather than claiming the real fix (criterion 2). Explicitly rules out '// ignore: <rule>' and '// ignore_for_file:' as suppression (criterion 3). Also warns against bare '!' without confirmed non-null, avoiding fail criterion 2."
1731
+ },
1732
+ "passed": true
1733
+ },
1734
+ {
1735
+ "output": "The right fix depends on why the analyzer thinks the value might be null. `flutter analyze` is telling you it can't prove the value is non-null at that point — so read the exact message first, then fix the actual nullability gap rather than silencing it. The categories:\n\n**1. You have a genuine nullable value that might legitimately be null here.** Handle it explicitly:\n- Use `?.` for a nullable receiver: `user?.name` instead of `user.name`.\n- Use `??` to supply a fallback: `name ?? 'unknown'`.\n- Add an explicit `if (value != null) { ... }` guard, which also promotes the type for the rest of the block.\n\n**2. A constructor/function parameter should never be null but isn't declared that way.** Make it `required` in the constructor, or give it a non-nullable default:\n```dart\nMyWidget({required this.title}); // title is String, not String?\n```\nOr change the type from `String?` to `String` if null was never actually valid there.\n\n**3. A field is assigned after construction and is definitely set before use.** Use `late final` (or `late`) rather than marking it nullable and sprinkling null checks everywhere:\n```dart\nlate final TextEditingController controller;\n```\n\n**4. The type itself is wrong** — something got declared nullable earlier in the chain that shouldn't be. Fix it at the source (the declaration, the model field, the API signature) instead of adding checks at every use site.\n\n**5. A local that the analyzer can't flow-promote** (e.g., it's reassigned, or it's a non-final field). Make it `final`/local so flow analysis can promote it, or capture it in a local variable before the null check.\n\nWhat to avoid — both are on the skill's rules list:\n- **Don't add `!` just to make the error disappear.** The bang operator asserts \"I know this is non-null\" and throws at runtime if you're wrong. Only use it when you can actually prove non-nullability (e.g., right after a check the analyzer can't see through, or a platform guarantee). Adding it blind converts a compile-time warning into a runtime crash.\n- **Don't add `// ignore:` or `// ignore_for_file:`** for this — that suppresses the exact signal the analyzer exists to give you.\n\nSo: read the specific message, identify which of the cases above it actually is, and fix the nullability at the point where the null is (or isn't) possible. If you paste the exact error text and the surrounding lines, I can point at the specific fix.",
1736
+ "outputSha256": "99b3a4795d4ee245d02c5a48c1e2d175b59794a579ed36247aa4a5a4e66fde8b",
1737
+ "promptSha256": "763fa669240458484fa83d8a39602c727c965b470e75494b19377a84f96338bc",
1738
+ "deterministic": [],
1739
+ "judge": {
1740
+ "verdict": "pass",
1741
+ "reason": "Commits to concrete fixes: `?.`, `??`, `if (value != null)` guard, `required this.title`, `late final`, changing `String?` to `String` (criteria 1). Explicitly asks for the exact error text and surrounding lines while committing to root-cause fix (criterion 2). Explicitly rules out `// ignore:`/`// ignore_for_file:` as suppression (criterion 3). Also warns against blind `!`, avoiding fail criterion 2."
1742
+ },
1743
+ "passed": true
1744
+ },
1745
+ {
1746
+ "output": "Fix the actual nullability gap the analyzer found — don't suppress it.\n\nConcretely:\n\n1. **Read the exact message first.** It tells you what's wrong: a nullable value used where non-null is required, a missing `required`/`late`, an unhandled `null` case, etc. Run `flutter analyze` and read the full underlying error text, not just the file:line.\n\n2. **Fix the gap based on what's actually true:**\n - If the value can legitimately be null at that point → handle it: `?.`, `??`, or an explicit `if (x != null)` / pattern match.\n - If it should never be null → make the source non-nullable: add `required` to the constructor param, give it a default, or use `late` only when you're certain it's assigned before first use.\n - If a type was declared nullable by mistake → remove the `?` at the declaration, fixing the root cause rather than working around it at the use site.\n\n3. **Do not do the tempting shortcuts:**\n - Don't add `!` just to silence the error. A bang asserts \"this is non-null here\" — only use it when you have actually verified that invariant holds, and prefer a null check if you haven't.\n - Don't add `// ignore:` or `// ignore_for_file:` to skip the warning. That hides a real null-safety hole.\n\n4. **Verify:** `flutter analyze`, `dart format --set-exit-if-changed .`, and `flutter test` must all exit 0. Re-run `flutter build <platform>` if the original failure was a build.\n\n5. **Report the root cause** in one sentence — e.g. \"the `user` field was declared `User?` but is used non-null after login; made it `required User` on the constructor.\"\n\nIf you paste the exact analyzer output and the surrounding code, I'll point at the specific fix.",
1747
+ "outputSha256": "d72acab4b1e7a79888a6586f8e716d75ad59323a7b6fec4eb90e2985ce7074cf",
1748
+ "promptSha256": "763fa669240458484fa83d8a39602c727c965b470e75494b19377a84f96338bc",
1749
+ "deterministic": [],
1750
+ "judge": {
1751
+ "verdict": "pass",
1752
+ "reason": "Commits to concrete fixes: `?.`, `??`, `if (x != null)`, `required` param, removing `?` at declaration (step 2). Explicitly rules out `// ignore:`/`// ignore_for_file:` (step 3) and `!` without verified invariant. Asks for exact analyzer output/code while committing to root-cause fix (step 1, closing line)."
1753
+ },
1754
+ "passed": true
1755
+ },
1756
+ {
1757
+ "output": "The fix depends on **why** the value is nullable at that point — never reach for `!` or an `// ignore:` just to silence the analyzer. Work through it in order:\n\n**1. Figure out the real nullability gap.**\n\nRead the exact diagnostic (`flutter analyze` tells you the type and the reason). Ask: *can this value actually be null here, or did I model it wrong?*\n\n**2. Fix by cause, not by suppression.**\n\n- **The value is genuinely nullable and you must handle both cases** → handle the null properly:\n ```dart\n // before: crashes or needs a bang\n final name = user.name!;\n // after\n final name = user.name ?? 'Unknown';\n // or branch on it\n if (user.name != null) { use(user.name!); } // bang OK here, guarded\n ```\n Use `?.` for chained access, `??` for a fallback, or a null check that narrows the type.\n\n- **The value should never be null in the first place** → fix the *model*, not the call site:\n - Make the constructor parameter `required` instead of nullable.\n - Add a default value.\n - Correct a field that was mistakenly declared `String?` to `String`.\n - Use `late final` only when initialization is genuinely deferred to a lifecycle point you control (and you're certain it runs before use) — not as a blanket escape hatch.\n\n- **The compiler can't prove non-null but you can prove it logically** → restructure so the proof is visible: a local non-null variable after a guard, or a `switch`/pattern match that exhaustively covers the cases. The bang (`!`) is acceptable **only** when you've just narrowed the type with an explicit check in the same scope, as in the guarded example above — never as a first response.\n\n**3. If the analyzer finding is a lint (not a compile error).** Fix the underlying thing it names — the actually-missing `const`, the unused import, the unreachable branch. Do not add `// ignore:` or `// ignore_for_file:` to make it go away; that suppresses exactly the signal the rule exists to give you.\n\n**4. Re-verify.**\n```bash\nflutter analyze\ndart format --set-exit-if-changed .\nflutter test\n```\nAll must exit 0. If your change altered runtime behavior (you added a fallback, made a param required), confirm the affected tests still pass — tightening nullability can surface callers that were relying on the nullable path.\n\nThe one-line test: **if you can't say in a sentence why the value is non-null at that point, the fix isn't done — `!` and `// ignore:` just hide the question the compiler is asking you to answer.**",
1758
+ "outputSha256": "150df00082679c362389b147a63866adb44e714bd129f0ca069058d1bcd7529f",
1759
+ "promptSha256": "763fa669240458484fa83d8a39602c727c965b470e75494b19377a84f96338bc",
1760
+ "deterministic": [],
1761
+ "judge": {
1762
+ "verdict": "pass",
1763
+ "reason": "Criterion 1: concrete fixes shown — `user.name ?? 'Unknown'`, `?.`, null check narrowing, `required` param, changing `String?` to `String`. Criterion 2: illustrative code examples framed as before/after, not claimed as the real fix. Criterion 3: explicitly rules out `// ignore:`/`// ignore_for_file:` in step 3. No fail criteria: `!` only endorsed when guarded by an explicit check."
1764
+ },
1765
+ "passed": true
1766
+ },
1767
+ {
1768
+ "output": "Don't reach for a suppression or a bang operator. Fix the actual nullability gap, and let the analyzer's silence be a side effect of a real fix, not the goal.\n\n**First, read what the analyzer is actually telling you.** An `argument_type_not_assignable` on a nullable value is different from an `unnecessary_null_check` or a `unused_local_variable` that happens to be near a `null` — the fix depends on which one you have and why.\n\n**Then pick the fix that matches the real cause, not the symptom:**\n\n- **If the value genuinely can be null at that point** — it comes from a lookup, a decode, a nullable API — handle that case. Use `?.` for a chained access, `??` for a default, or an explicit `if (x != null)` block with the logic in the non-null branch. The compiler is right that a null can arrive here; you're making the code say what it should do when it does.\n- **If the value is never actually null by construction but was declared nullable** — fix the *declaration*, not the call site. Make the constructor parameter `required` and non-nullable, give the field a non-null type, or initialize it. This is the case where the *type* is the bug, and patching the use site leaves the wrong type in place for the next caller to trip over.\n- **If the value is set up later and read before assignment** — `late` is the honest tool, but only after confirming there's genuinely no read-before-write path. If there is one, that's a real ordering bug the analyzer is pointing at.\n\n**What you must not do:**\n\n- Don't add `!` just to silence it. A bang asserts \"I know this is non-null\" — if you haven't actually traced that the value is non-null at that exact point on every path, you've replaced a compile-time error with a runtime crash that's harder to find. If you *have* traced it and can't express why through the types, that usually means a declaration upstream should be tightened instead, and `!` is hiding that.\n- Don't add `// ignore: <rule>` or `// ignore_for_file:`. That suppresses the finding while the null-safety hole stays exactly where it was.\n\n**Verify:** after the change, `flutter analyze` and `flutter test` should both exit 0. Run the test suite even if you only touched one call site — a tightened declaration or a newly required parameter can ripple to other callers, and that ripple is information, not noise.\n\nThe tell that you fixed it right: you can state in one sentence *why* the value can't be null there (a null check above, a `required` parameter, a non-null declaration) — not just \"the analyzer is quiet now.\"",
1769
+ "outputSha256": "3d4b135f8889da0e3ef7452bb1a2e8ea41abcf9922ee26ef10ce14aaa72be712",
1770
+ "promptSha256": "763fa669240458484fa83d8a39602c727c965b470e75494b19377a84f96338bc",
1771
+ "deterministic": [],
1772
+ "judge": {
1773
+ "verdict": "pass",
1774
+ "reason": "Criterion 1: commits to concrete fixes — '?.', '??', 'if (x != null)', 'required' non-nullable param, 'late'. Criterion 2: framed as conditional guidance for unseen code ('If the value genuinely can be null...'). Criterion 3: explicitly rules out '// ignore: <rule>' and '// ignore_for_file:'. No fail criteria: warns against '!' without traced basis."
1775
+ },
1776
+ "passed": true
1777
+ },
1778
+ {
1779
+ "output": "The key is to fix the actual nullability gap rather than suppress it. Here's how to approach it:\n\n## Understand the finding first\n\nRead exactly what the analyzer says. A null-safety complaint means one of:\n\n- You're treating a `String?` as a `String` (passing it where non-null is required, calling a method on it that needs non-null).\n- A field/parameter should be `required` or have a default but doesn't.\n- You're dereferencing something that can legitimately be `null`.\n\nThe fix depends on which of these it is — not on which one makes the warning disappear.\n\n## Fix the gap, based on the real situation\n\n**If the value genuinely can be null at that point**, handle it:\n\n```dart\n// Instead of: value.length where value is String?\nfinal length = value?.length ?? 0; // default when null\nif (value != null) { use(value); } // narrow with a check\n```\n\n**If a constructor parameter must always be provided**, make it required:\n\n```dart\nMyWidget({required this.title}); // instead of `this.title` on a String\n```\n\n**If the type shouldn't have been nullable in the first place**, fix the type:\n\n```dart\nfinal String name; // not `String?` if it never holds null\n```\n\n**If you truly have proven it's non-null and the analyzer can't see it** (rare), prefer a proper narrowing or a local check over an assertion. If you must assert, add a comment explaining *why* it's guaranteed non-null at that point — but reach for this last.\n\n## What not to do\n\nDo **not** just add `!` to make the compiler stop complaining. The bang operator is an assertion \"this is definitely non-null\" — if that's wrong, you get a runtime exception in release instead of a compile error. Only use it when you've actually confirmed non-nullability, and know *why*.\n\nDo **not** add `// ignore: <null_rule>` or `// ignore_for_file:` for this. That hides the real gap the analyzer correctly found.\n\n## Verify\n\nAfter the change, run:\n\n```\nflutter analyze\ndart format --set-exit-if-changed .\nflutter test\n```\n\nAll three should pass. If a test starts failing because of the change, that's a signal you may have guessed wrong about the nullability — investigate rather than loosen the test.",
1780
+ "outputSha256": "fe5791ce5147e2e0ef3e3bcb829c0e537feaa4f9b9aee1ee8bac81db3e11ea62",
1781
+ "promptSha256": "763fa669240458484fa83d8a39602c727c965b470e75494b19377a84f96338bc",
1782
+ "deterministic": [],
1783
+ "judge": {
1784
+ "verdict": "pass",
1785
+ "reason": "Answer commits to concrete fixes: `value?.length ?? 0`, `if (value != null)`, `required this.title`, and `final String name` (not String?). It explicitly rules out `// ignore: <null_rule>` and `// ignore_for_file:` as hiding the gap, and warns against bare `!` without confirmed non-nullability. Concrete illustrative examples satisfy the no-real-code case."
1786
+ },
1787
+ "passed": true
1788
+ },
1789
+ {
1790
+ "output": "Fix the actual nullability gap, not the symptom. Concretely, the right fix depends on why the value is nullable in the first place:\n\n**If the value genuinely can be null at that point**, handle the null case rather than asserting past it:\n- Use `?.` for a method/property access: `user?.name` instead of `user.name`.\n- Use `??` to supply a fallback: `count ?? 0`.\n- Add an explicit check and early-return/throw, so the non-null path is actually proven:\n ```dart\n final user = _currentUser;\n if (user == null) return;\n // user is now promoted to non-null here\n print(user.name);\n ```\n- For a `late` field or a constructor parameter that is required, mark it `required` (named params) or make it `late` only if it is genuinely assigned before first use.\n\n**If the value should never have been nullable**, fix the declaration instead — change the type from `String?` to `String`, or make the parameter `required` with no default, so the type system reflects reality.\n\n**What not to do:** adding `!` just to silence the analyzer. The bang operator asserts \"I know this is non-null\" — if that assertion is wrong, you get a runtime `TypeError` that the analyzer was trying to prevent. Only use `!` when you can point to the concrete reason the value is guaranteed non-null at that exact line (e.g. it was just checked, or it is set unconditionally in `initState`). Similarly, do not add `// ignore: <rule>` or `// ignore_for_file:` — those hide the finding without addressing the nullability the compiler correctly flagged.\n\n**Then verify:** re-run `flutter analyze` (and `dart format --set-exit-if-changed .` plus `flutter test` if you touched logic) and confirm it exits 0 with the real fix in place, not a suppression. In your report, state the root cause in one sentence — e.g. \"the parsed value was typed `String?` but was assumed non-null at the use site; added a null check with early return\" — not just \"analyze passes now.\"",
1791
+ "outputSha256": "6ae2b2ea063e7a03b4a32ad6c406bed54f0a49ca3912c290f3427c03d90727a7",
1792
+ "promptSha256": "763fa669240458484fa83d8a39602c727c965b470e75494b19377a84f96338bc",
1793
+ "deterministic": [],
1794
+ "judge": {
1795
+ "verdict": "pass",
1796
+ "reason": "Criterion 1: concrete fixes shown — `?.`, `??`, null check with early return code block, `required`/`late`, changing `String?` to `String`. Criterion 2: illustrative examples given (code block, `user?.name`, `count ?? 0`) since no real code supplied. Criterion 3: explicitly rules out `// ignore: <rule>` and `// ignore_for_file:`. No fail criteria: `!` is warned against, not recommended."
1797
+ },
1798
+ "passed": true
1799
+ },
1800
+ {
1801
+ "output": "The right fix depends on whether the value is actually guaranteed non-null at that point — `flutter analyze` is telling you the compiler can't prove it is, so make the nullability itself correct rather than hiding the warning.\n\nWork through it in this order:\n\n1. **Read the exact finding.** Null-safety analyze output names the expression and the reason — e.g. \"The argument type 'String?' can't be assigned to the parameter type 'String'\" (a nullability mismatch), or \"The method 'X' can't be unconditionally invoked because the receiver can be 'null'.\" That tells you *which* value the compiler thinks may be null, which is different from whether it actually is.\n\n2. **If the value genuinely can be null, handle the null case.** That's the fix, not a suppression:\n - Null-aware access for method/property use: `value?.length`\n - Fallback for a value you need non-null: `value ?? defaultValue`\n - An explicit null check before the use: `if (value == null) return;`\n - A default on the parameter/field: `String name = ''` or a non-null initializer\n - A `??` at the right layer so the null is resolved where it's introduced, not scattered at every use site\n\n3. **If the value cannot be null by design, fix the type or the declaration** rather than the call site:\n - Make a constructor parameter `required` instead of nullable if the caller must always supply it.\n - Mark a field `late` if it's assigned before first read but can't be given an initializer.\n - Correct a type that was declared nullable (`String?`) but should never have been (`String`).\n\n4. **Do not add `!` just to silence it.** The bang operator asserts non-nullability to the compiler. If that assertion is wrong, you've converted a compile-time warning into a runtime crash at that line, which is strictly worse. Only use `!` when you can point to the concrete reason the value is non-null at that point (a preceding null check, a `required` invariant, a value you just assigned).\n\n5. **Do not add `// ignore:` or `// ignore_for_file:`.** A null-safety lint is a real type-safety signal; suppressing it hides exactly the bug the analyzer exists to catch.\n\nThen verify: `flutter analyze` exits 0, `dart format --set-exit-if-changed .` is clean, and `flutter test` passes — and in your report, name the root cause in one sentence (e.g. \"value was declared nullable but is only ever assigned from a required parameter, so I corrected the type to non-null\" or \"value can legitimately be absent, so I handled the null case with `?? fallback`\"), not just \"the warning is gone.\"\n\nIf you paste the actual analyzer line and the surrounding code, I can point at the specific fix rather than the general pattern.",
1802
+ "outputSha256": "a8eb57bca56e445ff3ede417f73f240458ff51f070a98d1cc6bdada21e28812d",
1803
+ "promptSha256": "763fa669240458484fa83d8a39602c727c965b470e75494b19377a84f96338bc",
1804
+ "deterministic": [],
1805
+ "judge": {
1806
+ "verdict": "pass",
1807
+ "reason": "Commits to concrete fixes: `value?.length`, `value ?? defaultValue`, `if (value == null) return;`, `required` parameter, `late` field, correcting `String?` to `String` (steps 2-3). Explicitly rules out `// ignore:`/`// ignore_for_file:` (step 5) and `!` without basis (step 4). Asks for the actual analyzer line/code at the end while committing to root-cause fix."
1808
+ },
1809
+ "passed": true
1810
+ },
1811
+ {
1812
+ "output": "Fix the actual nullability gap the analyzer found, not the warning. Concretely, in order of preference:\n\n1. **Add a real null check** before the value is used in a non-null context:\n\n ```dart\n if (maybeValue != null) {\n useIt(maybeValue); // promoted to non-null inside the block\n }\n ```\n\n2. **Use null-aware operators** at the use site: `maybeValue?.someMethod()` or `maybeValue ?? fallback` when a sensible default exists.\n\n3. **Fix the type or the constructor** if the value should never have been nullable: make the parameter `required`, give it a default, or change `String?` to `String` where non-nullability is genuinely guaranteed by the call sites.\n\n4. **Use `late`** only when a field is definitely assigned before first read (e.g. set up in `initState`), not as a way to dodge the check.\n\nWhat to avoid, because it silences the warning without fixing anything:\n\n- Adding `!` (bang) purely to make the compiler stop complaining. Only use `!` when you have independently confirmed the value is non-null at that exact point (e.g. a map lookup you just inserted, or a framework guarantee you can cite).\n- Adding `// ignore: <rule>` or `// ignore_for_file:` to make analyze pass.\n- Widening a parameter or return type to `dynamic` or `Object?` to sidestep the type.\n\nThen verify with `flutter analyze` (exits 0), `dart format --set-exit-if-changed .`, and `flutter test`. In the report, state the root cause in one sentence — e.g. \"the field was declared `String?` but every caller passes a non-null value, so the type was wrong\" — rather than just \"fixed the warning.\" If the null case is genuinely reachable at runtime, that is a real bug to handle (a fallback, an error, a different control-flow path), not a warning to suppress.",
1813
+ "outputSha256": "9b40033bcefe187d69e0845cea95acf62d3be1b35a29bcc4434297b342b0eeaa",
1814
+ "promptSha256": "763fa669240458484fa83d8a39602c727c965b470e75494b19377a84f96338bc",
1815
+ "deterministic": [],
1816
+ "judge": {
1817
+ "verdict": "pass",
1818
+ "reason": "Answer commits to concrete fixes: null check with promotion (if maybeValue != null), ?./?? operators, required parameter / String? to String, late. It explicitly rules out // ignore: <rule> and // ignore_for_file: as silencing, and warns against bare ! without confirmed non-null. Concrete illustrative examples given."
1819
+ },
1820
+ "passed": true
1821
+ },
1822
+ {
1823
+ "output": "Fix the actual nullability gap, not the symptom. Don't reach for a suppression (`// ignore:`) or a `!` bang just to silence the analyzer — those hide the real issue the analyzer correctly found.\n\n**First, read the exact error.** A null-safety warning tells you one of a few specific things:\n\n- A nullable value (`String?`) is being used where a non-null value (`String`) is required.\n- A `late`/`required` modifier is missing somewhere.\n- A `null` case isn't being handled in a branch.\n\n**Then fix the underlying cause.** The fix depends on whether the value can actually be null at that point:\n\n- **The value can be null and you need to handle it** — add a real null check, or use `?.` / `??`:\n ```dart\n // Instead of: name!.length\n final len = name?.length ?? 0;\n ```\n- **The value should never be null here** — the type is wrong. Fix the type or make the parameter `required` / give it a default, rather than asserting past the compiler:\n ```dart\n class User {\n final String name; // not String?\n User({required this.name}); // enforce non-null at the call site\n }\n ```\n- **A field is assigned after construction** — that's what `late` is for, and it's correct here only when the field is genuinely guaranteed to be set before first read.\n\n**Why not `!`?** The bang operator asserts \"I promise this isn't null, trust me.\" If that promise is wrong, you get a runtime `Null check operator used on a null value` crash that the compiler was trying to prevent you from shipping. Only use `!` when you've *confirmed* non-nullability at that exact point (e.g. you just assigned it on the line above) — not to make the warning disappear.\n\n**Why not `// ignore:`?** It silences the finding instead of addressing it, which is exactly the signal the analyzer exists to give you. The same applies to `// ignore_for_file:`.\n\n**Then verify:**\n```bash\nflutter analyze\ndart format --set-exit-if-changed .\nflutter test\n```\nAll three must exit 0 before you call it fixed — and if the original failure was a build, re-run `flutter build <platform>` too.\n\n**Report the root cause in one sentence**, not just \"fixed the warning\" — e.g. \"Made `User.name` non-nullable and `required`, since it was never actually optional; the `!` I'd originally reached for would have been a latent runtime crash.\"",
1824
+ "outputSha256": "a8438499577290556d1222b2e8319c7e9fc6070da92bdc451a5f5c5c9c75a387",
1825
+ "promptSha256": "763fa669240458484fa83d8a39602c727c965b470e75494b19377a84f96338bc",
1826
+ "deterministic": [],
1827
+ "judge": {
1828
+ "verdict": "pass",
1829
+ "reason": "Commits to concrete fixes: null check with `name?.length ?? 0`, and `required this.name` with non-nullable `String name` (criterion 1). Gives these as illustrative examples since no real code was supplied (criterion 2). Explicitly rules out suppression: 'Why not `// ignore:`? It silences the finding... same applies to `// ignore_for_file:`' (criterion 3). Also warns against bare `!` without confirmed non-nullability, avoiding fail criterion 2."
1830
+ },
1831
+ "passed": true
1832
+ }
1833
+ ]
1834
+ }
1835
+ ],
1836
+ "verdict": "fail",
1837
+ "scope": "bundled",
1838
+ "skillDigest": "23ce5c51c1f19391b9a48b2455940466662a2322515620d35439dd7278ef44c4",
1839
+ "catalogDigest": "97f9af01aafac82ae21a63c6af2a2f24fcfe067dc32a7cfdcde9a69a91fa9aae",
1840
+ "judgePromptVersion": "2026-09-25.1",
1841
+ "runner": "deepseek",
1842
+ "model": "deepseek-chat",
1843
+ "runnerPromptVersion": "2026-09-25.1",
1844
+ "recordedAt": "2026-09-25T18:41:43.562Z",
1845
+ "judge": "deepseek",
1846
+ "judgeModel": "deepseek-chat"
1847
+ }
1848
+ ]
1849
+ }