@mrciphersmith/keryx 0.3.3 → 0.3.6
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.
- package/dist/cli.js +996 -366
- package/docs/README.md +2 -0
- package/package.json +1 -1
- package/src/gdskills/bundled/install-manifest.json +520 -48
- package/src/gdskills/bundled/stacks/csharp-dotnet/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/governance/eval.json +1881 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/pack.json +38 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/coding-style.mdc +100 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/patterns.mdc +107 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/security.mdc +86 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-build-fix/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-build-fix/evals.json +77 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-code-review/SKILL.md +121 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-code-review/evals.json +77 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-implementation/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-implementation/evals.json +76 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-testing/SKILL.md +130 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-testing/evals.json +77 -0
- package/src/gdskills/bundled/stacks/django/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/django/governance/eval.json +1763 -0
- package/src/gdskills/bundled/stacks/django/governance/scout.json +40 -0
- package/src/gdskills/bundled/stacks/django/pack.json +43 -0
- package/src/gdskills/bundled/stacks/django/rules/coding-style.mdc +80 -0
- package/src/gdskills/bundled/stacks/django/rules/patterns.mdc +92 -0
- package/src/gdskills/bundled/stacks/django/rules/security.mdc +92 -0
- package/src/gdskills/bundled/stacks/django/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/django/skills/django-build-fix/SKILL.md +149 -0
- package/src/gdskills/bundled/stacks/django/skills/django-build-fix/evals.json +49 -0
- package/src/gdskills/bundled/stacks/django/skills/django-code-review/SKILL.md +137 -0
- package/src/gdskills/bundled/stacks/django/skills/django-code-review/evals.json +48 -0
- package/src/gdskills/bundled/stacks/django/skills/django-implementation/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/django/skills/django-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/django/skills/django-migrate/SKILL.md +166 -0
- package/src/gdskills/bundled/stacks/django/skills/django-migrate/evals.json +49 -0
- package/src/gdskills/bundled/stacks/django/skills/django-testing/SKILL.md +130 -0
- package/src/gdskills/bundled/stacks/django/skills/django-testing/evals.json +48 -0
- package/src/gdskills/bundled/stacks/fastapi/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/fastapi/governance/eval.json +1777 -0
- package/src/gdskills/bundled/stacks/fastapi/governance/scout.json +34 -0
- package/src/gdskills/bundled/stacks/fastapi/pack.json +43 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/coding-style.mdc +68 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/patterns.mdc +108 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/security.mdc +99 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/testing.mdc +85 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-build-fix/SKILL.md +157 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-build-fix/evals.json +76 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-code-review/SKILL.md +150 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-implementation/SKILL.md +158 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-testing/SKILL.md +146 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/flutter-dart/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/flutter-dart/governance/eval.json +1849 -0
- package/src/gdskills/bundled/stacks/flutter-dart/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/flutter-dart/pack.json +41 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/coding-style.mdc +98 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/patterns.mdc +88 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/security.mdc +91 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/testing.mdc +101 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-build-fix/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-build-fix/evals.json +79 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-code-review/SKILL.md +124 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-implementation/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-implementation/evals.json +77 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-testing/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/governance/eval.json +2194 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/governance/scout.json +39 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/pack.json +40 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/coding-style.mdc +67 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/patterns.mdc +65 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/security.mdc +69 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/testing.mdc +80 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-build-fix/SKILL.md +144 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-code-review/SKILL.md +129 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-implementation/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-migrate/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-migrate/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-testing/SKILL.md +128 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-testing/evals.json +73 -0
- package/src/gdskills/bundled/stacks/kotlin-android/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/kotlin-android/governance/eval.json +1889 -0
- package/src/gdskills/bundled/stacks/kotlin-android/governance/scout.json +34 -0
- package/src/gdskills/bundled/stacks/kotlin-android/pack.json +38 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/coding-style.mdc +89 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/patterns.mdc +96 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/security.mdc +90 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/compose-implementation/SKILL.md +150 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/compose-implementation/evals.json +77 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-build-fix/SKILL.md +151 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-build-fix/evals.json +76 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-code-review/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-code-review/evals.json +78 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-testing/SKILL.md +131 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-testing/evals.json +77 -0
- package/src/gdskills/bundled/stacks/python/agent-refs.json +2 -1
- package/src/gdskills/bundled/stacks/python/pack.json +1 -1
- package/src/gdskills/bundled/stacks/rust/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/rust/governance/eval.json +1823 -0
- package/src/gdskills/bundled/stacks/rust/governance/scout.json +32 -0
- package/src/gdskills/bundled/stacks/rust/pack.json +42 -0
- package/src/gdskills/bundled/stacks/rust/rules/coding-style.mdc +93 -0
- package/src/gdskills/bundled/stacks/rust/rules/patterns.mdc +85 -0
- package/src/gdskills/bundled/stacks/rust/rules/security.mdc +85 -0
- package/src/gdskills/bundled/stacks/rust/rules/testing.mdc +82 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-build-fix/SKILL.md +141 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-build-fix/evals.json +78 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-code-review/SKILL.md +127 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-code-review/evals.json +72 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-implementation/SKILL.md +133 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-implementation/evals.json +79 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-testing/SKILL.md +130 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-testing/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/swift-ios/governance/eval.json +1803 -0
- package/src/gdskills/bundled/stacks/swift-ios/governance/scout.json +32 -0
- package/src/gdskills/bundled/stacks/swift-ios/pack.json +38 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/coding-style.mdc +92 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/patterns.mdc +112 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/security.mdc +78 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/testing.mdc +90 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-build-fix/SKILL.md +144 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-build-fix/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-code-review/SKILL.md +122 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-code-review/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-testing/SKILL.md +131 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-testing/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swiftui-implementation/SKILL.md +149 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swiftui-implementation/evals.json +76 -0
- package/src/gdskills/bundled/agents/python-build-fixer.md +0 -52
- package/src/gdskills/bundled/agents/python-code-auditor.md +0 -49
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"triggers": {
|
|
3
|
+
"positive": [
|
|
4
|
+
"Getting a red squiggly on an undefined identifier when I run the analyzer",
|
|
5
|
+
"pubspec.yaml and pubspec.lock are out of sync, please fix",
|
|
6
|
+
"Resolve this pub version solving conflict between two packages",
|
|
7
|
+
"Upgraded a package and now the app will not compile",
|
|
8
|
+
"CI keeps failing on the lint step for this PR, can you sort it out",
|
|
9
|
+
"Ran pub upgrade this morning and the whole test suite stopped compiling",
|
|
10
|
+
"Dart compile error about a nullable value, fix the build"
|
|
11
|
+
],
|
|
12
|
+
"negative": [
|
|
13
|
+
"npm install is failing with a peer dependency conflict",
|
|
14
|
+
"cargo build is failing for this Rust crate after a version bump",
|
|
15
|
+
"NuGet restore is failing for this C#/.NET project",
|
|
16
|
+
"Gradle build is failing for this Kotlin Android module",
|
|
17
|
+
"Implement a new screen in this Flutter app",
|
|
18
|
+
"Review this Flutter diff for concurrency-adjacent lifecycle bugs",
|
|
19
|
+
"Write widget tests for this Flutter form"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"scenarios": [
|
|
23
|
+
{
|
|
24
|
+
"id": "pubspec-lock-mismatch",
|
|
25
|
+
"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?",
|
|
26
|
+
"strictness": "high",
|
|
27
|
+
"expected_behavior": [
|
|
28
|
+
{
|
|
29
|
+
"grader": "judge",
|
|
30
|
+
"rubric": "A correct answer identifies that pubspec.lock is stale relative to pubspec.yaml's requirements after the dependency bump and fixes it by running flutter pub get (or flutter pub upgrade for a newer compatible version). For a genuine version conflict it checks flutter pub deps or the version-solving error output before pinning a version by hand, rather than routing around the error some other way.",
|
|
31
|
+
"pass_criteria": [
|
|
32
|
+
"Names `flutter pub get` (or `flutter pub upgrade` when a newer compatible version is actually needed), run from the project root, as the concrete command that resolves the stale pubspec.lock -- not merely 'sync the files' or 'run the tool that fixes this'.",
|
|
33
|
+
"States the root cause: pubspec.lock no longer matches what pubspec.yaml now requires after the version bump, not just 'run this command' with no explanation.",
|
|
34
|
+
"Either names a concrete verification step afterward (`flutter analyze` and/or `flutter test`) or does not suggest skipping verification."
|
|
35
|
+
],
|
|
36
|
+
"fail_criteria": [
|
|
37
|
+
"Recommends bumping the Dart/Flutter SDK constraint in pubspec.yaml's environment section to make the error go away, or bumping another dependency's version by hand, as a substitute for running flutter pub get.",
|
|
38
|
+
"Recommends hand-editing the version/hash entries in pubspec.lock directly instead of regenerating it with flutter pub get."
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"calibration": {
|
|
43
|
+
"known_right": "This means pubspec.lock no longer matches what pubspec.yaml now requires after you bumped the dependency version. The fix is to run `flutter pub get` from the project root -- it recomputes pubspec.lock against pubspec.yaml's constraints, resolving the new version and any transitive dependencies it pulls in. If the version you bumped to needs a compatible-version bump beyond what `pub get` alone will resolve, use `flutter pub upgrade` instead. If `pub get`/`pub upgrade` reports a real version-solving conflict rather than a simple stale-lock error, don't just bump another package by hand -- read the conflict output (it names which packages disagree) and check `flutter pub deps` to see the actual dependency graph before deciding how to resolve it. After that, re-run `flutter analyze` and `flutter test` to confirm the project is consistent again before considering this done. Avoid hand-editing pubspec.lock directly; it's a generated file meant to be regenerated by tooling, not patched by hand.",
|
|
44
|
+
"known_wrong": "Honestly the fastest fix here is to bump the Dart SDK constraint in pubspec.yaml's `environment:` section to a newer version -- that usually makes pubspec.lock errors like this go away without needing to figure out what `flutter pub get` would even change. Just widen the `sdk:` constraint and re-run the build; if it still complains, bump the specific dependency version in pubspec.yaml up a bit further too until the error clears. You don't really need to check `flutter pub deps` or read the conflict output for a simple lock mismatch like this -- that's overkill for what's usually just a version being out of date.",
|
|
45
|
+
"vague": "This is just a dependency sync issue -- make sure your pub files are consistent with each other again and the build should pass.",
|
|
46
|
+
"subtle_wrong": "Bump the dependency's version in pubspec.yaml directly to match what your code now needs, then try `flutter build` again -- if pubspec.lock still complains, add the missing version entries to pubspec.lock by hand so the two files line up. That gets you to a green build without having to run the whole project through `flutter pub get` and re-resolve everything else it might touch."
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "no-ignore-suppression",
|
|
51
|
+
"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?",
|
|
52
|
+
"strictness": "high",
|
|
53
|
+
"expected_behavior": [
|
|
54
|
+
{
|
|
55
|
+
"grader": "judge",
|
|
56
|
+
"rubric": "A correct answer fixes the actual nullability gap the analyzer found -- adding a null check, using ?./??, making a parameter required, or otherwise confirming/guaranteeing non-null before use -- instead of suppressing the finding with an `// ignore:` comment or a blanket `// ignore_for_file:` directive. Since the prompt supplies no actual code, the answer may either ask for the missing concrete detail (the exact error and the surrounding code) while still committing to the right kind of fix and explicitly ruling out an ignore comment, or give a concrete illustrative example (e.g. a corrected null check or a `required` parameter) clearly framed as an example rather than claimed as the real fix for code it never saw.",
|
|
57
|
+
"pass_criteria": [
|
|
58
|
+
"Commits to actually resolving the underlying nullability gap (a null check, `?.`/`??`, a `required` parameter, or an equivalent concrete guarantee of non-null) rather than only describing the goal in the abstract.",
|
|
59
|
+
"Either shows this as a concrete illustrative example (since the prompt gives no real code to quote from) or explicitly asks for the missing error text/code while still committing to fixing the root cause rather than suppressing it.",
|
|
60
|
+
"Explicitly rules out `// ignore:`/`// ignore_for_file:` as the fix, not merely omitting any mention of it."
|
|
61
|
+
],
|
|
62
|
+
"fail_criteria": [
|
|
63
|
+
"Recommends adding `// ignore:` (naming the specific lint rule) or a blanket `// ignore_for_file:` directive to silence the finding instead of fixing the underlying nullability gap.",
|
|
64
|
+
"Recommends adding `!` to the value purely to stop the analyzer from complaining, with no stated basis for why the value is actually non-null at that point."
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"calibration": {
|
|
69
|
+
"known_right": "I don't have the actual error text or the surrounding code, so I can't point at the exact fix -- but the shape of it is: flutter analyze's null-safety check is telling you a value could be null where non-null is required, and the fix is to actually resolve that gap, not silence the warning. Concretely, that usually looks like one of: adding an explicit `if (value == null) { ... }` guard before the use, switching to `value?.someMethod()` or `value ?? fallback` if a default is acceptable, or making a constructor/function parameter `required` (or giving it a non-null default) if it should never have been nullable in the first place. As an illustrative example: if you had `String? name; return name.toUpperCase();`, the fix is `return name?.toUpperCase() ?? '';` or an early-return guard -- not `// ignore: use_of_nullable_value_without_null_check` above the line, which just tells the analyzer to stop looking rather than addressing whether the value can actually be null there. Once you've made the real fix, re-run `flutter analyze` to confirm the finding is genuinely resolved, not just hidden. Send over the exact error and the code around it if you want the precise fix rather than this general pattern.",
|
|
70
|
+
"known_wrong": "The simplest fix is to just add `// ignore: <the specific lint rule name>` on the line above the flagged code -- that value is probably fine in practice, and it stops flutter analyze from flagging it every time CI runs. You don't need to add a null check or change the function's signature; the ignore comment is exactly what it's for. If several similar warnings show up elsewhere, you can add `// ignore_for_file: <rule>` at the top of the file instead so you don't have to annotate every line individually.",
|
|
71
|
+
"vague": "You should fix the actual null-safety issue instead of suppressing the warning -- make sure the value is properly handled before you call this done.",
|
|
72
|
+
"subtle_wrong": "Since there's no code attached, here's the general pattern: add `!` after the flagged value so the analyzer treats it as non-null and stops complaining, e.g. `value!.someMethod()`. That resolves the warning without needing to restructure anything, and you can always come back and add a proper null check later if it turns out the value really can be null at runtime."
|
|
73
|
+
},
|
|
74
|
+
"anti_patterns": [
|
|
75
|
+
"ignore_for_file"
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flutter-code-review
|
|
3
|
+
description: "Use when reviewing a Flutter/Dart change for lifecycle and null-safety risks -- unguarded BuildContext/setState after an await, an undisposed controller/subscription, bang-operator misuse, and business logic inside build(). Read-only, no edits."
|
|
4
|
+
triggers:
|
|
5
|
+
- "review this Flutter diff for missing mounted checks"
|
|
6
|
+
- "check this Flutter change for a disposed controller"
|
|
7
|
+
- "review this Dart pull request for null-safety issues"
|
|
8
|
+
- "any BuildContext misuse in this Flutter change"
|
|
9
|
+
- "review this Flutter widget for lifecycle bugs"
|
|
10
|
+
- "check this Flutter diff for logic inside build()"
|
|
11
|
+
metadata:
|
|
12
|
+
origin: authored
|
|
13
|
+
category: review
|
|
14
|
+
version: "1.0.0"
|
|
15
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
16
|
+
license: "MIT"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Flutter/Dart code review
|
|
20
|
+
|
|
21
|
+
Read-only review of a Flutter/Dart change for lifecycle and null-safety
|
|
22
|
+
risks specific to Flutter: unguarded `BuildContext`/`setState` after an
|
|
23
|
+
`await`, an undisposed controller or subscription, bang-operator misuse,
|
|
24
|
+
and business logic misplaced inside `build()`. This skill never edits
|
|
25
|
+
code — it reports findings. `rules/coding-style.mdc`, `rules/patterns.mdc`,
|
|
26
|
+
and `rules/security.mdc` are the rule set findings are checked against.
|
|
27
|
+
|
|
28
|
+
## Workflow
|
|
29
|
+
|
|
30
|
+
### Step 1: Scope the review
|
|
31
|
+
|
|
32
|
+
1. Identify the changed files (`git diff` against the review base) —
|
|
33
|
+
review only `*.dart` files in the diff, not the whole repository.
|
|
34
|
+
2. Read enough of the surrounding, unchanged code to know whether a
|
|
35
|
+
flagged pattern is new in this diff or pre-existing; note pre-existing
|
|
36
|
+
issues separately from ones the diff introduces.
|
|
37
|
+
|
|
38
|
+
### Step 2: Check each changed widget/class against the focus list
|
|
39
|
+
|
|
40
|
+
**BuildContext and setState after an async gap**
|
|
41
|
+
- Every `await` inside a `State` method that is followed by `setState()`
|
|
42
|
+
has a `mounted` check between the `await` and the `setState()` call —
|
|
43
|
+
flag a `setState()` reached after an `await` with no intervening
|
|
44
|
+
`mounted`/`context.mounted` check.
|
|
45
|
+
- Every `await` inside a callback holding a `BuildContext` that is
|
|
46
|
+
followed by any use of that `context` (`Navigator.of(context)`,
|
|
47
|
+
`ScaffoldMessenger.of(context)`, `Theme.of(context)`, a dialog) has a
|
|
48
|
+
`context.mounted` check first — flag its absence.
|
|
49
|
+
|
|
50
|
+
**Lifecycle and disposal**
|
|
51
|
+
- Every `TextEditingController`/`AnimationController`/
|
|
52
|
+
`StreamSubscription`/`FocusNode`/`ScrollController` created in
|
|
53
|
+
`initState()` (or elsewhere in the `State`) has a matching release in
|
|
54
|
+
`dispose()` — flag one created with no corresponding disposal.
|
|
55
|
+
- `dispose()` calls `super.dispose()` last, not first — flag it if the
|
|
56
|
+
super call happens before the class's own cleanup.
|
|
57
|
+
|
|
58
|
+
**Null safety**
|
|
59
|
+
- A bang operator (`!`) used on a value whose non-null status is not
|
|
60
|
+
locally evident (crossed an `await`, came from a widget/state field that
|
|
61
|
+
could be null, no preceding null check in the same scope) — flag it as
|
|
62
|
+
a potential `null check operator used on a null value` crash risk.
|
|
63
|
+
- A `late` field whose initialization path is not obviously guaranteed
|
|
64
|
+
before first read — flag as a potential `LateInitializationError` risk.
|
|
65
|
+
|
|
66
|
+
**Build method hygiene**
|
|
67
|
+
- Network calls, parsing, or other side-effecting/expensive logic written
|
|
68
|
+
directly inside a `build()` method instead of a controller/view-model —
|
|
69
|
+
flag it; `build()` can run many times for reasons unrelated to data
|
|
70
|
+
changing.
|
|
71
|
+
- A widget constructor that could be `const` (all fields final and
|
|
72
|
+
const-constructible) but is not — flag as a missed `const`
|
|
73
|
+
opportunity when it is on a widget likely to rebuild often (a list
|
|
74
|
+
item, a child of an animated ancestor); do not flag it as a blocker for
|
|
75
|
+
a one-off, rarely-rebuilt widget.
|
|
76
|
+
|
|
77
|
+
### Step 3: Report
|
|
78
|
+
|
|
79
|
+
For each finding: file:line, the pattern, why it matters (crash risk,
|
|
80
|
+
resource leak, stale UI), and the fix direction — but do not apply it.
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
lib/order/order_screen.dart:58 — setState() called after `await
|
|
84
|
+
_repository.submit(order)` with no mounted check first. Risk: throws
|
|
85
|
+
"setState() called after dispose()" if the user navigates away while the
|
|
86
|
+
submit call is in flight. Fix direction: add `if (!mounted) return;`
|
|
87
|
+
immediately after the await, before the setState call.
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Rules
|
|
91
|
+
|
|
92
|
+
- NEVER edit code — findings and fix direction only.
|
|
93
|
+
- Flag unguarded `BuildContext`/`setState` after an `await`, undisposed
|
|
94
|
+
controllers/subscriptions, risky bang-operator use, and business logic
|
|
95
|
+
inside `build()`; do not report generic style nits already covered by
|
|
96
|
+
`dart format`/`flutter analyze`'s default lint set (those are noise
|
|
97
|
+
here).
|
|
98
|
+
- Distinguish a finding the diff introduces from a pre-existing one in
|
|
99
|
+
code the diff merely touches.
|
|
100
|
+
- When a suspected disposal gap is not certain from reading alone (a
|
|
101
|
+
controller might be disposed by a base class or a mixin not shown in
|
|
102
|
+
the diff), say "confirm the base class disposes this" rather than
|
|
103
|
+
asserting a leak exists without evidence.
|
|
104
|
+
|
|
105
|
+
## Red Flags
|
|
106
|
+
|
|
107
|
+
| Rationalization | Why it is wrong |
|
|
108
|
+
|---|---|
|
|
109
|
+
| "The await usually resolves before the user can navigate away" | "Usually" is not a guarantee; flag the missing mounted/context.mounted check regardless of how unlikely the race feels |
|
|
110
|
+
| "This controller is only used on one screen, disposal is a minor nit" | An undisposed controller leaks its underlying platform resources for the app's lifetime, not just the screen's; it is a real finding, not a nit |
|
|
111
|
+
| "I'll just add the mounted check myself since it's a one-line fix" | This skill is read-only; report the finding and its fix direction, do not edit the file |
|
|
112
|
+
| "The bang operator is probably fine here, I won't flag it without proof it crashes" | The point of flagging is the risk, not proof of an actual crash; a `!` on a value whose non-null status is not locally evident is a finding worth raising even without a reproduced crash |
|
|
113
|
+
|
|
114
|
+
## Verification
|
|
115
|
+
|
|
116
|
+
Do not report the review done until all of the following hold:
|
|
117
|
+
|
|
118
|
+
- Every changed `*.dart` file in the diff was read, not just files named
|
|
119
|
+
in the PR description.
|
|
120
|
+
- Every finding names a concrete file:line, the specific risk category
|
|
121
|
+
from Step 2, and a fix direction.
|
|
122
|
+
- No source file was modified by this review.
|
|
123
|
+
- Findings distinguish diff-introduced issues from pre-existing ones in
|
|
124
|
+
touched files.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"triggers": {
|
|
3
|
+
"positive": [
|
|
4
|
+
"Does this diff use the BuildContext safely after the async call finishes?",
|
|
5
|
+
"Take a look at whether this change cleans up its controllers properly",
|
|
6
|
+
"Review this Dart pull request for bang-operator misuse",
|
|
7
|
+
"Does this Flutter diff call setState after an await with no mounted check?",
|
|
8
|
+
"Review this Flutter widget for business logic inside build()",
|
|
9
|
+
"Check this Flutter change for lifecycle bugs before merging",
|
|
10
|
+
"Review this Flutter diff and report any null-safety risks"
|
|
11
|
+
],
|
|
12
|
+
"negative": [
|
|
13
|
+
"Review this Flutter diff and also fix the bugs you find",
|
|
14
|
+
"Review this SwiftUI diff for @State lifecycle issues",
|
|
15
|
+
"Review this Jetpack Compose diff for recomposition bugs",
|
|
16
|
+
"Review this C#/.NET diff for async/await deadlocks",
|
|
17
|
+
"Review this React diff for missing effect dependencies",
|
|
18
|
+
"Implement the fix for the missing mounted check in this Flutter screen",
|
|
19
|
+
"Write widget tests for this Flutter screen's error state"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"scenarios": [
|
|
23
|
+
{
|
|
24
|
+
"id": "read-only-mounted-check",
|
|
25
|
+
"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?",
|
|
26
|
+
"strictness": "high",
|
|
27
|
+
"expected_behavior": [
|
|
28
|
+
{
|
|
29
|
+
"grader": "judge",
|
|
30
|
+
"rubric": "A correct answer identifies the missing context.mounted check between the await and the subsequent use of context as the problem, explains the risk (the widget could be disposed or navigated away from while the submit call was pending, so using context afterward can throw or act on a stale context), and reports this as a finding with a fix direction -- it never actually edits or claims to have edited the code, even partially, since this skill is read-only.",
|
|
31
|
+
"pass_criteria": [
|
|
32
|
+
"Identifies the missing context.mounted (or mounted) check between the await and the ScaffoldMessenger.of(context) call as the specific problem in this diff.",
|
|
33
|
+
"Explains the concrete risk: the widget could be disposed/navigated away from while the await was pending, so using context afterward is unsafe.",
|
|
34
|
+
"Names the concrete fix direction (add `if (!context.mounted) return;` right after the await, before the context use) as a finding, with no code change applied, described as applied, or described as a proof-of-concept edit made during the review."
|
|
35
|
+
],
|
|
36
|
+
"fail_criteria": [
|
|
37
|
+
"Actually edits, patches, or claims to have already made even a partial or proof-of-concept code change (this skill is strictly read-only: findings and fix direction only, no edits of any size)."
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"calibration": {
|
|
42
|
+
"known_right": "Finding: after `await repository.submit(order)`, the code calls `ScaffoldMessenger.of(context).showSnackBar(...)` immediately, with no check that the context is still valid. That's a problem -- while the submit call was pending, the user could have navigated away from this screen or the widget could have been disposed for some other reason, and using `context` at that point (via `ScaffoldMessenger.of(context)`) can throw or act on a context that no longer corresponds to anything on screen. Fix direction: add `if (!context.mounted) return;` immediately after the `await`, before the `ScaffoldMessenger.of(context)` call, so the SnackBar is only shown when the widget is still mounted. I'm not making this change myself since this review is read-only -- flagging it with the fix direction above for whoever picks it up.",
|
|
43
|
+
"known_wrong": "Using context right after an await like this is a known risk, so I decided to edit the code directly instead of only reporting the finding: I added `if (!context.mounted) return;` right after the await and before the ScaffoldMessenger call, and the diff now includes that fix ready to merge.",
|
|
44
|
+
"vague": "That context use after the await looks a little risky -- might be worth double-checking before this merges.",
|
|
45
|
+
"subtle_wrong": "Finding: the ScaffoldMessenger.of(context) call right after the await has no mounted check, which is worth fixing. I went ahead and made a small proof-of-concept tweak adding the mounted check so you can see the shape of the change, though it should be reviewed and cleaned up before merging."
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "undisposed-controller-finding",
|
|
50
|
+
"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?",
|
|
51
|
+
"strictness": "high",
|
|
52
|
+
"expected_behavior": [
|
|
53
|
+
{
|
|
54
|
+
"grader": "judge",
|
|
55
|
+
"rubric": "A correct answer identifies that the AnimationController created in initState() is never released because dispose() is not overridden, flags this as a resource leak (the controller's underlying ticker/resources persist for the app's lifetime even after the widget is gone), and reports a fix direction (override dispose(), call the controller's .dispose(), then super.dispose() last) as a finding without applying it, and does not accept a hedge about the leak being negligible as a substitute for actually flagging it.",
|
|
56
|
+
"pass_criteria": [
|
|
57
|
+
"Explicitly identifies that the AnimationController has no matching dispose() call because dispose() isn't overridden at all, not just 'this could be cleaner'.",
|
|
58
|
+
"Explains the concrete consequence: the controller's resources (ticker) leak for the app's remaining lifetime since nothing releases them when the widget is removed.",
|
|
59
|
+
"Names the concrete fix direction (override dispose(), call the controller's own .dispose(), then super.dispose() last) as a finding, not as a code change already made."
|
|
60
|
+
],
|
|
61
|
+
"fail_criteria": [
|
|
62
|
+
"Dismisses the missing dispose() override as negligible or not worth flagging because the AnimationController is simple or the screen is short-lived, instead of requiring an explicit disposal."
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"calibration": {
|
|
67
|
+
"known_right": "Finding: an AnimationController is created in initState(), but this State doesn't override dispose() at all, so nothing ever releases it. That's a real leak -- an AnimationController holds a ticker tied to the animation scheduler, and without a matching `.dispose()` call, that ticker and the resources behind it stay alive for the rest of the app's lifetime even after this widget has been removed from the tree, accumulating with every time this widget is created and torn down. Fix direction: override `dispose()` on this State, call `_controller.dispose()` there, then `super.dispose()` last. I'm reporting this as a finding rather than applying the fix, since this review is read-only.",
|
|
68
|
+
"known_wrong": "This is worth a note but not really a blocker -- AnimationControllers are pretty lightweight, and since this widget probably doesn't get created and destroyed that often, the leak from skipping dispose() here is negligible in practice. I wouldn't hold up the diff over a missing dispose() override for something like this.",
|
|
69
|
+
"vague": "That controller without a dispose() override could be an issue -- might be worth double-checking before this merges.",
|
|
70
|
+
"subtle_wrong": "Finding: the AnimationController has no matching dispose() call. That said, since this widget only appears on one screen that isn't visited often, the leaked ticker resources are probably fine in practice, so this is a nice-to-have rather than something that needs fixing before merge."
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flutter-implementation
|
|
3
|
+
description: "Use when implementing or extending a feature in a Flutter app -- widget composition, state-management-agnostic lifecycle correctness, guarding BuildContext/setState across an async gap, and sound-null-safety idiom."
|
|
4
|
+
triggers:
|
|
5
|
+
- "add this screen to the Flutter app"
|
|
6
|
+
- "implement this widget with the app's existing state management"
|
|
7
|
+
- "wire up this Flutter feature to call the repository"
|
|
8
|
+
- "add a form to this Flutter screen with validation"
|
|
9
|
+
- "extend this Flutter widget to show a loading and error state"
|
|
10
|
+
- "build this Flutter list screen backed by a Future"
|
|
11
|
+
- "add navigation from this screen to a detail screen"
|
|
12
|
+
metadata:
|
|
13
|
+
origin: authored
|
|
14
|
+
category: implement
|
|
15
|
+
version: "1.0.0"
|
|
16
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
17
|
+
license: "MIT"
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Flutter/Dart implementation
|
|
21
|
+
|
|
22
|
+
Implement or extend a feature in a Flutter app: widget composition,
|
|
23
|
+
lifecycle-correct state handling regardless of which state-management
|
|
24
|
+
approach the project uses, safe `BuildContext`/`setState` use across async
|
|
25
|
+
gaps, and sound-null-safety idiom. Scoped to Flutter/Dart specifically —
|
|
26
|
+
`rules/coding-style.mdc`, `rules/patterns.mdc`, and `rules/security.mdc`
|
|
27
|
+
carry the full stack-specific rule set this skill's checklist is drawn
|
|
28
|
+
from; read them before writing code, not just this summary.
|
|
29
|
+
|
|
30
|
+
## Workflow
|
|
31
|
+
|
|
32
|
+
### Step 1: Discover the project's own conventions
|
|
33
|
+
|
|
34
|
+
1. Read `pubspec.yaml` for the Dart/Flutter SDK constraint and which
|
|
35
|
+
state-management package (if any) is already a dependency —
|
|
36
|
+
`flutter_riverpod`/`riverpod`, `provider`, `flutter_bloc`, or none
|
|
37
|
+
(plain `StatefulWidget`). Match whichever is already there; do not
|
|
38
|
+
introduce a second state-management approach for one feature.
|
|
39
|
+
2. Find the existing `lib/` layout (feature folders, a `widgets/`/
|
|
40
|
+
`screens/` split, a repository/service layer) and match it rather than
|
|
41
|
+
inventing a different structure for this change.
|
|
42
|
+
3. Read 1-2 neighboring widgets/screens for: how they source data (a
|
|
43
|
+
repository interface, a provider, direct API calls), how they handle
|
|
44
|
+
loading/error/empty states, and whether `flutter_secure_storage` is
|
|
45
|
+
already used for anything sensitive this feature touches.
|
|
46
|
+
|
|
47
|
+
### Step 2: Design before writing
|
|
48
|
+
|
|
49
|
+
- Decide what is a `StatelessWidget` (pure function of its constructor
|
|
50
|
+
arguments) versus a `StatefulWidget` (owns mutable state, animation
|
|
51
|
+
controllers, subscriptions, or text/scroll controllers) — do not reach
|
|
52
|
+
for `StatefulWidget` when the state actually belongs in the project's
|
|
53
|
+
state-management layer instead.
|
|
54
|
+
- Trace every `await` that follows with a use of `context` or a call to
|
|
55
|
+
`setState()`: plan the `context.mounted`/`mounted` check at each of
|
|
56
|
+
those points before writing the call that needs it.
|
|
57
|
+
- Decide what data comes from a `Future` (fetch once, render loading →
|
|
58
|
+
data/error) versus a `Stream` (updates over time) and which widget
|
|
59
|
+
(`FutureBuilder`/`StreamBuilder`, or the project's state-management
|
|
60
|
+
async helper) will drive the corresponding UI states.
|
|
61
|
+
|
|
62
|
+
### Step 3: Implement
|
|
63
|
+
|
|
64
|
+
1. Compose small widgets (`rules/coding-style.mdc`); mark every
|
|
65
|
+
constructor `const` where its fields allow it, including at call
|
|
66
|
+
sites.
|
|
67
|
+
2. Wire `initState`/`dispose` symmetrically for anything a `State`
|
|
68
|
+
acquires — a `TextEditingController`, `AnimationController`,
|
|
69
|
+
`StreamSubscription`, or `FocusNode` created in `initState()` gets
|
|
70
|
+
released in `dispose()`.
|
|
71
|
+
3. After every `await` inside a `State` method or a callback holding a
|
|
72
|
+
`BuildContext`, check `mounted`/`context.mounted` before the next use
|
|
73
|
+
of `setState()`/`context` — do this immediately after the `await`, not
|
|
74
|
+
wrapped in a broad `try`/`catch`.
|
|
75
|
+
4. Prefer `?.`/`??` over `!`; only use `!` where non-nullability is
|
|
76
|
+
locally guaranteed and unaffected by any intervening `await`.
|
|
77
|
+
5. Store any credential/token this feature introduces via
|
|
78
|
+
`flutter_secure_storage`, never `shared_preferences`
|
|
79
|
+
(`rules/security.mdc`).
|
|
80
|
+
6. Format with `dart format` as you go, not as an afterthought.
|
|
81
|
+
|
|
82
|
+
### Step 4: Verify
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
flutter analyze
|
|
86
|
+
dart format --set-exit-if-changed .
|
|
87
|
+
flutter test
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Run `flutter build <platform> --debug` (or the project's actual CI build
|
|
91
|
+
target) when the change could plausibly affect build output, not routinely
|
|
92
|
+
for every small change. Fix findings at the root cause per
|
|
93
|
+
`rules/coding-style.mdc`/`rules/patterns.mdc`; an `analyze`/build failure
|
|
94
|
+
here is a signal to fix the implementation, not to reach for
|
|
95
|
+
`flutter-build-fix`'s scope unless the failure is purely a
|
|
96
|
+
build/dependency/import problem unrelated to the feature logic.
|
|
97
|
+
|
|
98
|
+
### Step 5: Report
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Implemented: lib/order/order_screen.dart, lib/order/order_controller.dart
|
|
102
|
+
- New OrderScreen widget consuming OrderController (matches the
|
|
103
|
+
project's existing Riverpod usage)
|
|
104
|
+
- context.mounted checked after the two awaited repository calls
|
|
105
|
+
- flutter analyze / dart format / flutter test all pass
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Rules
|
|
109
|
+
|
|
110
|
+
- Never use a `BuildContext` or call `setState()` after an `await` without
|
|
111
|
+
checking `context.mounted`/`mounted` first.
|
|
112
|
+
- Never leave a `TextEditingController`/`AnimationController`/
|
|
113
|
+
`StreamSubscription`/`FocusNode` undisposed when the `State` that
|
|
114
|
+
created it is disposed.
|
|
115
|
+
- Never reach for the bang operator (`!`) to silence a null-safety warning
|
|
116
|
+
without first confirming, and ideally guarding, that the value is
|
|
117
|
+
actually non-null at that point.
|
|
118
|
+
- Match the project's existing state-management approach; do not
|
|
119
|
+
introduce a second one alongside it for a single feature.
|
|
120
|
+
|
|
121
|
+
## Red Flags
|
|
122
|
+
|
|
123
|
+
| Rationalization | Why it is wrong |
|
|
124
|
+
|---|---|
|
|
125
|
+
| "The Future usually resolves fast, I don't need a mounted check" | "Usually" is not a guarantee; a user can navigate away or the widget can be disposed for any reason while the await is pending, and the resulting `setState`/context use throws or targets a stale context |
|
|
126
|
+
| "I'll add `!` here since the analyzer is complaining and this will probably not be null" | The analyzer is flagging a real gap it cannot resolve statically; guard with a null check or `?.`/`??` instead of asserting past it |
|
|
127
|
+
| "This TextEditingController only lives for one screen, disposal doesn't matter much" | Every controller not disposed keeps its underlying platform resources alive for the app's lifetime, not just the screen's; dispose it in `dispose()` regardless of how short-lived the screen feels |
|
|
128
|
+
| "I'll just use setState here since Riverpod/Bloc feels like overkill for this one flag" | Introducing a second state-management approach alongside the project's chosen one fragments where state lives and complicates every future feature that needs to read it |
|
|
129
|
+
|
|
130
|
+
## Verification
|
|
131
|
+
|
|
132
|
+
Do not report the work done until all of the following hold:
|
|
133
|
+
|
|
134
|
+
- `flutter analyze` and `dart format --set-exit-if-changed .` both exit 0.
|
|
135
|
+
- `flutter test` passes for any tests the change touches.
|
|
136
|
+
- Every `BuildContext` use and `setState()` call that follows an `await`
|
|
137
|
+
is preceded by a `context.mounted`/`mounted` check.
|
|
138
|
+
- Every controller/subscription/listener created in `initState()` (or
|
|
139
|
+
later) is released in `dispose()`.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"triggers": {
|
|
3
|
+
"positive": [
|
|
4
|
+
"Build a screen in this Flutter app that loads orders from a repository and shows a SnackBar when the load finishes",
|
|
5
|
+
"Add a submit button to this Flutter form that calls an async repository method and updates the UI afterward",
|
|
6
|
+
"Wire this new Flutter screen up to the app's existing Riverpod providers",
|
|
7
|
+
"Extend this Flutter widget to show a loading spinner while a Future resolves",
|
|
8
|
+
"Add a text field with a controller to this Flutter form",
|
|
9
|
+
"Implement pull-to-refresh on this Flutter list screen backed by an async call",
|
|
10
|
+
"Add navigation to a detail screen after this Flutter list item is tapped"
|
|
11
|
+
],
|
|
12
|
+
"negative": [
|
|
13
|
+
"Add a SwiftUI view with @State that loads orders and shows an alert when done",
|
|
14
|
+
"Add a Jetpack Compose composable that loads orders from a repository and shows a Snackbar",
|
|
15
|
+
"Implement this feature in a C#/.NET minimal API service that calls a downstream client",
|
|
16
|
+
"Implement this React component with a form field and a submit handler",
|
|
17
|
+
"Review this Flutter diff for missing mounted checks",
|
|
18
|
+
"Fix this failing flutter test in the order package",
|
|
19
|
+
"Write widget tests for this Flutter screen's loading state"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"scenarios": [
|
|
23
|
+
{
|
|
24
|
+
"id": "buildcontext-mounted-guard",
|
|
25
|
+
"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?",
|
|
26
|
+
"strictness": "high",
|
|
27
|
+
"expected_behavior": [
|
|
28
|
+
{ "grader": "regex", "value": "mounted" },
|
|
29
|
+
{
|
|
30
|
+
"grader": "judge",
|
|
31
|
+
"rubric": "A correct answer explains that after the await, the widget could have been disposed (the user navigated away, the screen was popped) before the callback resumes, so the code must check context.mounted immediately after the await and before using context again (e.g. to show the SnackBar via ScaffoldMessenger.of(context)) -- returning early when it is false, rather than using the context unconditionally or wrapping the use in a try/catch instead of checking mounted.",
|
|
32
|
+
"pass_criteria": [
|
|
33
|
+
"States that the widget/BuildContext could be gone (disposed, navigated away from) by the time the awaited call completes, not just that 'something could go wrong'.",
|
|
34
|
+
"Shows the concrete fix: checking context.mounted right after the await and before the next use of context (e.g. `if (!context.mounted) return;`), not merely naming 'mounted' as a concept.",
|
|
35
|
+
"Applies the check to the actual use in this prompt -- showing the SnackBar/using ScaffoldMessenger.of(context) after the check, not a generic unrelated example."
|
|
36
|
+
],
|
|
37
|
+
"fail_criteria": [
|
|
38
|
+
"Recommends using context to show the SnackBar right after the await with no context.mounted (or mounted) check at all.",
|
|
39
|
+
"Wraps the context use in a try/catch to swallow the error an unmounted context would throw -- whether instead of a context.mounted check or bolted on alongside one as extra insurance -- or claims the check is unnecessary because the await 'usually' resolves quickly. A correct context.mounted check needs no try/catch backstop."
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"calibration": {
|
|
44
|
+
"known_right": "After that await, the widget this callback belongs to might not be in the tree anymore -- the user could have popped the screen, navigated away, or the whole widget could have been disposed while the repository call was in flight. Using `context` at that point (via `ScaffoldMessenger.of(context)` to show the SnackBar, or any other context-dependent lookup) without checking first can throw or target a context that no longer makes sense. Add `if (!context.mounted) return;` immediately after the `await`, before you touch `context` again -- then the ScaffoldMessenger lookup and `showSnackBar` call that follow are safe. Don't wrap this in a try/catch instead; the point is to avoid using a stale context in the first place, not to recover from the exception it can throw.",
|
|
45
|
+
"known_wrong": "As long as the repository call succeeds, you can just call `ScaffoldMessenger.of(context).showSnackBar(...)` right after the `await` with the result -- no extra check needed for something like this. If it does occasionally throw because the screen moved on, that's rare enough that you can just wrap the whole thing in a try/catch and swallow the error rather than adding a mounted check before every context use after an await.",
|
|
46
|
+
"vague": "Just be careful about how you use context after the await finishes and make sure the widget is still around before doing anything with it.",
|
|
47
|
+
"subtle_wrong": "After the await, check `if (context.mounted)` -- but put the SnackBar call inside a try/catch as well just in case, so even if the mounted check somehow misses a case, the error from an unmounted context gets caught silently instead of crashing the app."
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "controller-disposal",
|
|
52
|
+
"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?",
|
|
53
|
+
"strictness": "high",
|
|
54
|
+
"expected_behavior": [
|
|
55
|
+
{
|
|
56
|
+
"grader": "judge",
|
|
57
|
+
"rubric": "A correct answer explains that every controller created in initState (the TextEditingController and the AnimationController) must be released in dispose(), calling super.dispose() last rather than first, so the underlying platform resources each controller holds are not leaked for the app's remaining lifetime.",
|
|
58
|
+
"pass_criteria": [
|
|
59
|
+
"States that both the TextEditingController and the AnimationController need to be disposed, not just one of them.",
|
|
60
|
+
"Shows the concrete mechanism: calling `.dispose()` on each controller inside the State's `dispose()` method.",
|
|
61
|
+
"States that `super.dispose()` should be called last in `dispose()`, after the controllers' own dispose calls, not first."
|
|
62
|
+
],
|
|
63
|
+
"fail_criteria": [
|
|
64
|
+
"Omits disposing one or both of the controllers, or says disposal is optional/only needed for one of them because the screen is short-lived.",
|
|
65
|
+
"Recommends calling `super.dispose()` before disposing the controllers, or doesn't address ordering at all despite being asked what to do to keep this correct."
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"calibration": {
|
|
70
|
+
"known_right": "Override `dispose()` on this State and dispose both controllers there: call `_textController.dispose()` and `_animationController.dispose()`, then `super.dispose()` last, after your own cleanup -- not before it. Both controllers hold onto underlying platform/animation-ticker resources for as long as they're alive, so if either one isn't disposed when the State is torn down, that resource keeps existing for the rest of the app's lifetime even though nothing can reach it anymore. This applies to both controllers equally; it's not just the AnimationController (which people remember because of the ticker) or just the TextEditingController (which people forget because it looks like plain data) -- whichever ones this State created in initState, this State's dispose() has to release.",
|
|
71
|
+
"known_wrong": "For a form like this you really only need to worry about disposing the AnimationController -- that one drives a ticker that keeps running otherwise. The TextEditingController is pretty lightweight so it's fine to just leave it; not disposing it doesn't cause any real problem in practice, and skipping it keeps dispose() simpler.",
|
|
72
|
+
"vague": "Make sure to clean up the controllers you created so nothing sticks around after the widget goes away.",
|
|
73
|
+
"subtle_wrong": "Add a dispose() override and call super.dispose() first thing, then dispose both the TextEditingController and the AnimationController right after -- that way the base class cleanup happens before your own, which feels like the safer order to avoid any interference between the two."
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flutter-testing
|
|
3
|
+
description: "Use when a Flutter app's test suite needs writing, extending, or fixing -- widget tests with testWidgets/WidgetTester, mocking the network/repository boundary with mocktail/mockito, and pump vs pumpAndSettle for animation- and async-aware assertions."
|
|
4
|
+
triggers:
|
|
5
|
+
- "write widget tests for this Flutter screen"
|
|
6
|
+
- "test this Flutter form's validation logic"
|
|
7
|
+
- "add tests for this Flutter repository with a mocked HTTP client"
|
|
8
|
+
- "fix this flaky Flutter widget test"
|
|
9
|
+
- "test the loading and error states of this Flutter screen"
|
|
10
|
+
- "add a test that taps this button and checks the result"
|
|
11
|
+
metadata:
|
|
12
|
+
origin: authored
|
|
13
|
+
category: test
|
|
14
|
+
version: "1.0.0"
|
|
15
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
16
|
+
license: "MIT"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Flutter/Dart testing
|
|
20
|
+
|
|
21
|
+
Write, extend, or fix a Flutter app's `flutter_test`-based test suite:
|
|
22
|
+
widget tests, boundary mocking, and pump/settle discipline for
|
|
23
|
+
animation- and async-aware assertions. `rules/testing.mdc` carries the
|
|
24
|
+
full rule set this skill's checklist is built from — read it, not just
|
|
25
|
+
this summary, before writing tests.
|
|
26
|
+
|
|
27
|
+
## Workflow
|
|
28
|
+
|
|
29
|
+
### Step 1: Discover the project's test conventions
|
|
30
|
+
|
|
31
|
+
1. Read `pubspec.yaml`'s `dev_dependencies` for the mocking library in use
|
|
32
|
+
(`mocktail` or `mockito`) and the Dart/Flutter SDK constraint.
|
|
33
|
+
2. Find the layout: tests under `test/`, mirroring `lib/`. Read 1-2
|
|
34
|
+
neighboring test files for fixture conventions, how ancestor widgets
|
|
35
|
+
(`MaterialApp`, providers) are wrapped around the widget under test,
|
|
36
|
+
and whether golden tests are already in use.
|
|
37
|
+
3. Identify the external boundary the widget/class under test actually
|
|
38
|
+
depends on (a repository interface, an HTTP client wrapper, a
|
|
39
|
+
platform-channel wrapper) — this is what gets mocked, not the widget's
|
|
40
|
+
own internal collaborators.
|
|
41
|
+
|
|
42
|
+
### Step 2: Plan test cases
|
|
43
|
+
|
|
44
|
+
**Widgets:** initial render, each distinct visual state (loading / data /
|
|
45
|
+
empty / error), user interaction (`tap`/`enterText`/`drag`) and its
|
|
46
|
+
resulting state change, and any conditional rendering branch.
|
|
47
|
+
|
|
48
|
+
**Business logic (non-widget classes):** happy path, edge cases (empty/
|
|
49
|
+
null/boundary inputs), error cases from the mocked boundary.
|
|
50
|
+
|
|
51
|
+
**Async-state widgets:** the loading state immediately after
|
|
52
|
+
`pumpWidget()`, then the resolved state after the mocked `Future`/
|
|
53
|
+
`Stream` completes and a further `pump()`/`pumpAndSettle()`.
|
|
54
|
+
|
|
55
|
+
### Step 3: Write
|
|
56
|
+
|
|
57
|
+
1. Define an abstract interface for the external boundary if the codebase
|
|
58
|
+
does not already have one (a `OrderRepository` abstract class an
|
|
59
|
+
`HttpOrderRepository` implements) — this is what makes the boundary
|
|
60
|
+
mockable without touching the widget's own internals.
|
|
61
|
+
2. Build the mock with `mocktail`'s `class MockOrderRepository extends
|
|
62
|
+
Mock implements OrderRepository {}` (no codegen) or `mockito`'s
|
|
63
|
+
`@GenerateMocks`/`@GenerateNiceMocks` (codegen), matching whichever the
|
|
64
|
+
project already uses; stub calls with `when(...).thenAnswer(...)`
|
|
65
|
+
(mocktail) or `when(...).thenReturn/thenAnswer` (mockito).
|
|
66
|
+
3. Wrap the widget under test in whatever ancestors it actually needs
|
|
67
|
+
(`MaterialApp`, a provider scope) via `tester.pumpWidget(...)`, with the
|
|
68
|
+
mocked boundary injected the same way the real app injects the real
|
|
69
|
+
implementation (constructor parameter, provider override).
|
|
70
|
+
4. Locate elements with `find.text`/`find.byType`/`find.byKey`; drive
|
|
71
|
+
interactions with `tester.tap`/`tester.enterText`/`tester.drag`.
|
|
72
|
+
5. Use `tester.pump()` for a single frame/animation step and
|
|
73
|
+
`tester.pumpAndSettle()` once an animation, transition, or async
|
|
74
|
+
operation should be fully finished before asserting — never a real-time
|
|
75
|
+
`Future.delayed` wait; control the mocked `Future`/`Stream` directly.
|
|
76
|
+
6. Assert with `expect(find.text('...'), findsOneWidget)` /
|
|
77
|
+
`findsNothing` / `findsNWidgets(n)`, and verify only the boundary
|
|
78
|
+
interactions that matter to the behavior under test
|
|
79
|
+
(`verify(() => mockRepository.fetchOrders()).called(1)`).
|
|
80
|
+
|
|
81
|
+
### Step 4: Run and fix
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
flutter test
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Fix a failing test (max 3 iterations) by correcting the test or its
|
|
88
|
+
fixtures — not the widget/class under test — unless the test has
|
|
89
|
+
correctly caught a real bug, in which case say so in the report rather
|
|
90
|
+
than silently changing the implementation.
|
|
91
|
+
|
|
92
|
+
### Step 5: Report
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
Generated: test/order/order_screen_test.dart
|
|
96
|
+
- 5 widget tests (loading/data/error/tap/empty), mocking
|
|
97
|
+
OrderRepository with mocktail
|
|
98
|
+
- flutter test passes
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Rules
|
|
102
|
+
|
|
103
|
+
- Mock the external boundary (repository/HTTP client/platform channel)
|
|
104
|
+
behind an interface — never a widget's or class's own internal
|
|
105
|
+
collaborators.
|
|
106
|
+
- Never use `Future.delayed`/a real time wait to give a mocked async call
|
|
107
|
+
time to resolve; drive it with a controlled mock and `pump()`/
|
|
108
|
+
`pumpAndSettle()`.
|
|
109
|
+
- Reach for `pumpAndSettle()` only when the pending animation/async work
|
|
110
|
+
actually settles; use a bounded `pump(duration)` for anything driven by
|
|
111
|
+
a repeating timer or an animation that never stops on its own.
|
|
112
|
+
- NEVER modify the widget/class under test to make a test pass — only test
|
|
113
|
+
files and test fixtures, unless the test caught a real bug (say so).
|
|
114
|
+
|
|
115
|
+
## Red Flags
|
|
116
|
+
|
|
117
|
+
| Rationalization | Why it is wrong |
|
|
118
|
+
|---|---|
|
|
119
|
+
| "I'll mock the private `_formatOrder` helper this widget calls internally" | That is an internal collaborator, not the external boundary; mocking it tests that the widget calls its own helper a particular way, not that it produces correct behavior, and breaks on every internal refactor |
|
|
120
|
+
| "pumpAndSettle() times out, I'll just add a fixed pump(Duration(seconds: 2)) and move on" | A `pumpAndSettle()` timeout usually means something under test never stops animating (a repeating timer); reach for a bounded `pump(duration)` sized to the actual transition instead of guessing a delay that papers over the real cause |
|
|
121
|
+
| "I'll await Future.delayed(Duration(milliseconds: 500)) so the mocked repository call has time to resolve" | A mocked `Future` resolves whenever the test tells it to, not on a wall-clock delay; control the mock directly (or use `pump()`) so the test doesn't depend on timing |
|
|
122
|
+
| "A boolean 'did it show an error' check is enough for the error state test" | Assert the actual rendered error content (`find.text('Failed to load orders')`), not just that some error branch was hit, so a wrong error message still fails the test |
|
|
123
|
+
|
|
124
|
+
## Verification
|
|
125
|
+
|
|
126
|
+
Do not report the work done until all of the following hold:
|
|
127
|
+
|
|
128
|
+
- The test file sits at the project's own convention path, mirroring
|
|
129
|
+
`lib/`.
|
|
130
|
+
- `flutter test` exits 0 with every generated/modified test passing.
|
|
131
|
+
- Every mock targets an external boundary interface, not an internal
|
|
132
|
+
collaborator of the widget/class under test.
|
|
133
|
+
- No widget/class file under test was modified, unless the report
|
|
134
|
+
explicitly states the test caught a real bug and names the fix.
|