@mrciphersmith/keryx 0.3.3 → 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.
- package/dist/cli.js +125 -51
- package/package.json +1 -1
- package/src/gdskills/bundled/install-manifest.json +231 -2
- 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/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/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/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
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"query": "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
|
+
"decision": "fork",
|
|
5
|
+
"topMatch": "flutter-dart/flutter-code-review",
|
|
6
|
+
"recordedAt": "2026-09-25T15:35:12.436Z",
|
|
7
|
+
"skillName": "flutter-implementation",
|
|
8
|
+
"justification": "Top match flutter-dart/flutter-code-review is this same pack's read-only review skill (different intent, action verb, workflow), not a real implementation duplicate; no existing implement skill covers Flutter widget composition, BuildContext-across-async-gap guarding, or Dart null safety."
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"query": "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.",
|
|
12
|
+
"decision": "fork",
|
|
13
|
+
"topMatch": "ts-js-node/nodejs-testing",
|
|
14
|
+
"recordedAt": "2026-09-25T15:35:12.736Z",
|
|
15
|
+
"skillName": "flutter-testing",
|
|
16
|
+
"justification": "Nearest match ts-js-node/nodejs-testing tests TypeScript/Node code with Vitest/Jest, a different runtime and tooling entirely; no existing test skill covers Flutter widget tests, testWidgets/WidgetTester, or pump/pumpAndSettle semantics."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"query": "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.",
|
|
20
|
+
"decision": "create",
|
|
21
|
+
"topMatch": "flutter-dart/flutter-implementation",
|
|
22
|
+
"recordedAt": "2026-09-25T15:33:56.877Z",
|
|
23
|
+
"skillName": "flutter-code-review"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"query": "Use when flutter analyze/flutter build fails, or pubspec.yaml/pubspec.lock are out of sync -- resolves dependency version conflicts, analyzer/lint failures, null-safety compile errors, and a failing flutter test, with the smallest root-cause fix.",
|
|
27
|
+
"decision": "fork",
|
|
28
|
+
"topMatch": "kotlin-android/kotlin-android-build-fix",
|
|
29
|
+
"recordedAt": "2026-09-25T15:35:13.311Z",
|
|
30
|
+
"skillName": "flutter-build-fix",
|
|
31
|
+
"justification": "Nearest match kotlin-android/kotlin-android-build-fix fixes Gradle/AGP/Kotlin/detekt errors, not flutter analyze/pubspec/Dart null-safety compile errors; each build-fix skill is scoped to its own toolchain's specific failure surface."
|
|
32
|
+
}
|
|
33
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "flutter-dart",
|
|
3
|
+
"family": "framework",
|
|
4
|
+
"modules": ["flutter-dart-rules", "flutter-dart-skills"],
|
|
5
|
+
"detectionMarkers": ["flutter"],
|
|
6
|
+
"provenance": {
|
|
7
|
+
"origin": "authored",
|
|
8
|
+
"sourceRef": "flow 336, Wave 4 batch 4"
|
|
9
|
+
},
|
|
10
|
+
"stability": "experimental",
|
|
11
|
+
"skills": {
|
|
12
|
+
"implement": ["flutter-implementation"],
|
|
13
|
+
"test": ["flutter-testing"],
|
|
14
|
+
"review": ["flutter-code-review"],
|
|
15
|
+
"build-fix": ["flutter-build-fix"],
|
|
16
|
+
"migrate": []
|
|
17
|
+
},
|
|
18
|
+
"agentProfile": {
|
|
19
|
+
"displayName": "Flutter/Dart",
|
|
20
|
+
"auditFocus": [
|
|
21
|
+
"a BuildContext used after an await with no context.mounted (or State.mounted) check first",
|
|
22
|
+
"a StatefulWidget with a TextEditingController/AnimationController/StreamSubscription/FocusNode created in initState but never released in dispose",
|
|
23
|
+
"setState called after an async gap with no mounted guard, risking 'setState() called after dispose()'",
|
|
24
|
+
"a widget tree many levels deep in one build method instead of extracted into smaller const-friendly widgets",
|
|
25
|
+
"a secret, token, or API key read from or written to SharedPreferences instead of flutter_secure_storage",
|
|
26
|
+
"a bang operator (!) on a nullable value with no preceding null check or non-null guarantee in scope"
|
|
27
|
+
],
|
|
28
|
+
"buildCommands": [
|
|
29
|
+
"flutter analyze",
|
|
30
|
+
"dart format --set-exit-if-changed .",
|
|
31
|
+
"flutter test",
|
|
32
|
+
"flutter build apk --debug (or the project's actual target platform build)"
|
|
33
|
+
],
|
|
34
|
+
"fixGuardrails": [
|
|
35
|
+
"never add // ignore: or a blanket // ignore_for_file: comment to silence an analyzer finding instead of fixing the root cause",
|
|
36
|
+
"never add the bang operator (!) to a nullable value just to satisfy the analyzer without first establishing the value is actually non-null there",
|
|
37
|
+
"never delete or weaken a failing widget test's expectation (find.text/find.byType assertion) to make it pass",
|
|
38
|
+
"never pin or downgrade a pub dependency to route around a real incompatibility without saying so in the report"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.dart"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Flutter/Dart coding style
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic style rules to modern Dart
|
|
11
|
+
(3.x, sound null safety — mandatory since Dart 3, it cannot be disabled)
|
|
12
|
+
and Flutter widget idiom. Applies only to `*.dart` files — everything not
|
|
13
|
+
Dart/Flutter-specific still comes from the common rules this file
|
|
14
|
+
`extends`.
|
|
15
|
+
|
|
16
|
+
## Naming and structure
|
|
17
|
+
|
|
18
|
+
- `UpperCamelCase` for classes, enums, extensions, typedefs, and widgets;
|
|
19
|
+
`lowerCamelCase` for variables, parameters, and methods;
|
|
20
|
+
`lowercase_with_underscores` for file names and package/directory names —
|
|
21
|
+
never `snake_case` identifiers or `PascalCase` file names.
|
|
22
|
+
- A widget file is named after the widget it defines
|
|
23
|
+
(`order_summary_card.dart` for `OrderSummaryCard`), one primary public
|
|
24
|
+
widget per file, matching how the project already splits `lib/`.
|
|
25
|
+
- Keep a `build()` method reading top-to-bottom as layout, not a mix of
|
|
26
|
+
layout and business logic — push data fetching, formatting, and
|
|
27
|
+
validation into a controller/view-model/state object the widget reads
|
|
28
|
+
from, not inline inside `build()`.
|
|
29
|
+
- An identifier's name shrinks with its scope: a short-lived loop index is
|
|
30
|
+
`i`, a public widget or service class gets a full descriptive name.
|
|
31
|
+
|
|
32
|
+
## Const constructors and rebuild cost
|
|
33
|
+
|
|
34
|
+
- Mark every widget constructor `const` when all of its fields can be
|
|
35
|
+
`final` and const-constructible — a `const` widget is skipped entirely
|
|
36
|
+
by `build()` on a parent rebuild instead of being rebuilt and diffed,
|
|
37
|
+
which is a real, measurable performance difference in a widget tree
|
|
38
|
+
that rebuilds often (a list item, an animated ancestor).
|
|
39
|
+
- Prefer `const` at the call site too (`const SizedBox(height: 8)`, `const
|
|
40
|
+
Divider()`), not just on the constructor declaration — the analyzer's
|
|
41
|
+
`prefer_const_constructors` lint exists to catch the call sites that
|
|
42
|
+
could be const but are not; do not disable that lint to avoid fixing
|
|
43
|
+
them.
|
|
44
|
+
- A widget with a non-const field (a callback closure built fresh each
|
|
45
|
+
`build()`, for instance) cannot be const — when that field does not
|
|
46
|
+
actually need to vary per-build, hoist it out (a `static` handler, a
|
|
47
|
+
field on the enclosing `State`) so the widget itself can go back to
|
|
48
|
+
being const.
|
|
49
|
+
|
|
50
|
+
## Sound null safety idiom
|
|
51
|
+
|
|
52
|
+
- Prefer `?.`/`??`/`??=` over the bang operator (`!`) — reach for `!` only
|
|
53
|
+
when the value's non-nullability is guaranteed by code the analyzer
|
|
54
|
+
cannot see (already checked a few lines above with no intervening
|
|
55
|
+
`await`, or a documented invariant), and say why in a comment when it is
|
|
56
|
+
not obvious.
|
|
57
|
+
- Never use `!` on a value that crossed an `await` since it was last
|
|
58
|
+
checked, or on a field read from a widget/state that could have been
|
|
59
|
+
disposed in between — re-check (`if (value == null) return;` or
|
|
60
|
+
`context.mounted`) after the async gap instead of re-asserting non-null.
|
|
61
|
+
- Declare a field `late` only when it is genuinely always initialized
|
|
62
|
+
before first read (e.g. in `initState`) — a `late` field that can
|
|
63
|
+
legitimately be unset just trades a compile-time nullability warning for
|
|
64
|
+
a runtime `LateInitializationError`, which is strictly worse.
|
|
65
|
+
- Give a nullable parameter a sensible default via `??` at the point of
|
|
66
|
+
use rather than threading a null check through every downstream
|
|
67
|
+
consumer of that parameter.
|
|
68
|
+
|
|
69
|
+
## Composition over deep nesting
|
|
70
|
+
|
|
71
|
+
- Extract a widget into its own class (or a private `_SectionWidget`
|
|
72
|
+
below the file's main widget) once a single `build()` method's nesting
|
|
73
|
+
becomes hard to scan — composition of small, focused widgets is the
|
|
74
|
+
idiomatic Flutter equivalent of extracting a function, and each
|
|
75
|
+
extracted piece can independently be `const` and independently testable
|
|
76
|
+
with `testWidgets`.
|
|
77
|
+
- Prefer a `Column`/`Row`/`Wrap` plus a handful of small child widgets
|
|
78
|
+
over one large widget expression with many positional/named-argument
|
|
79
|
+
levels of `Padding(child: Container(child: Column(children: [...])))` —
|
|
80
|
+
flatten with `SizedBox`/`Padding` siblings and extracted widgets instead
|
|
81
|
+
of nesting purely to achieve a visual effect a flatter tree would give
|
|
82
|
+
just as well.
|
|
83
|
+
- A `build()` method that branches on more than two or three conditions to
|
|
84
|
+
decide what to render is a sign the widget is doing too much — extract
|
|
85
|
+
each branch into its own small widget so each one is readable and
|
|
86
|
+
testable in isolation.
|
|
87
|
+
|
|
88
|
+
## Formatting and imports
|
|
89
|
+
|
|
90
|
+
- Format with `dart format` before finishing a change — do not hand-format
|
|
91
|
+
around a formatter that is already configured; CI enforces `dart format
|
|
92
|
+
--set-exit-if-changed .` in most Flutter repos.
|
|
93
|
+
- Order imports `dart:` / `package:flutter` / other `package:` / relative,
|
|
94
|
+
with a blank line between groups, matching whatever the project's
|
|
95
|
+
`analysis_options.yaml` (e.g. `directives_ordering`) already enforces.
|
|
96
|
+
- Prefer relative imports within the same package's `lib/` tree and
|
|
97
|
+
`package:` imports for anything outside it, matching the project's
|
|
98
|
+
existing convention rather than switching styles mid-file.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.dart"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Flutter/Dart patterns
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic design guidance to Flutter's
|
|
11
|
+
widget lifecycle, state handling, and async patterns. Applies only to
|
|
12
|
+
`*.dart` files. Flutter has no single mandated state-management story —
|
|
13
|
+
`Provider`, `Riverpod`, `Bloc`, and plain `StatefulWidget` all appear
|
|
14
|
+
across real projects — so this rule gives lifecycle-correct guidance that
|
|
15
|
+
applies regardless of which the project has chosen; match whichever one is
|
|
16
|
+
already in use rather than introducing a second approach.
|
|
17
|
+
|
|
18
|
+
## Widget lifecycle correctness
|
|
19
|
+
|
|
20
|
+
- `initState()` is for one-time setup that needs `this` (subscribing to a
|
|
21
|
+
controller, starting a timer, reading an inherited widget via
|
|
22
|
+
`context.dependOnInheritedWidgetOfExactType` is not yet safe here — use
|
|
23
|
+
`didChangeDependencies()` for that); it must call `super.initState()`
|
|
24
|
+
first.
|
|
25
|
+
- `dispose()` releases everything `initState()` (or a later lifecycle
|
|
26
|
+
method) acquired: cancel every `StreamSubscription`, call `.dispose()`
|
|
27
|
+
on every `TextEditingController`/`AnimationController`/`FocusNode`/
|
|
28
|
+
`ScrollController` the `State` created, and call `super.dispose()` last,
|
|
29
|
+
not first. A controller created in `initState` and never disposed leaks
|
|
30
|
+
its underlying resources for the life of the app.
|
|
31
|
+
- `didUpdateWidget(oldWidget)` is where a `StatefulWidget` reacts to a
|
|
32
|
+
changed constructor parameter from its parent (e.g. re-subscribing to a
|
|
33
|
+
different id) — comparing `oldWidget.someId != widget.someId` there,
|
|
34
|
+
not inside `build()`, keeps that reaction from re-running on every
|
|
35
|
+
rebuild.
|
|
36
|
+
|
|
37
|
+
## setState and BuildContext after an async gap
|
|
38
|
+
|
|
39
|
+
- After any `await` inside a `State` method, check `mounted` (the
|
|
40
|
+
`State`'s own property) before calling `setState()` — a `State` can be
|
|
41
|
+
disposed while the `Future` was pending, and calling `setState()` on a
|
|
42
|
+
disposed `State` throws.
|
|
43
|
+
- After any `await` inside a callback that holds a `BuildContext` (a
|
|
44
|
+
button's `onPressed`, a `Future` continuation), check `context.mounted`
|
|
45
|
+
before using that `context` again — for showing a dialog/SnackBar,
|
|
46
|
+
reading `Theme.of(context)`/`Navigator.of(context)`, or anything else —
|
|
47
|
+
never assume the widget that owns it is still in the tree just because
|
|
48
|
+
the `Future` completed. This applies whether the context came from a
|
|
49
|
+
`StatefulWidget`'s own `State` or from a `StatelessWidget`'s `build`
|
|
50
|
+
parameter.
|
|
51
|
+
- An early-return `if` check on `context.mounted` (or `mounted` inside
|
|
52
|
+
`State`) immediately after the `await`, before the first subsequent use
|
|
53
|
+
of `context`/`setState`, is the fix — not a `try`/`catch` around the
|
|
54
|
+
eventual error, and not skipping the check because the async call
|
|
55
|
+
"usually" resolves quickly.
|
|
56
|
+
|
|
57
|
+
## Async patterns
|
|
58
|
+
|
|
59
|
+
- Prefer `async`/`await` over chained `.then()` for anything beyond a
|
|
60
|
+
single continuation — sequential `await` reads top-to-bottom and
|
|
61
|
+
composes with `try`/`catch` the same way synchronous code does.
|
|
62
|
+
- For a value that arrives over time rather than once, use a `Stream` and
|
|
63
|
+
a `StreamBuilder` (or the state-management approach's own stream/async
|
|
64
|
+
listener), not a `Future` polled on a timer.
|
|
65
|
+
- Cancel a `StreamSubscription` created outside `StreamBuilder` (i.e. one
|
|
66
|
+
the `State` subscribes to manually) in `dispose()` — `StreamBuilder`
|
|
67
|
+
itself manages its subscription's lifecycle and needs no manual cancel.
|
|
68
|
+
- Guard a `FutureBuilder`/`StreamBuilder`'s `snapshot.connectionState`
|
|
69
|
+
before reading `snapshot.data` — a `ConnectionState.waiting` or
|
|
70
|
+
`.none` snapshot with a stale or null `data` rendered as if it were
|
|
71
|
+
ready is a common source of a flashed empty/error state on first frame.
|
|
72
|
+
|
|
73
|
+
## Anti-patterns to flag
|
|
74
|
+
|
|
75
|
+
- A `TextEditingController`/`AnimationController`/`StreamSubscription`/
|
|
76
|
+
`FocusNode` created in `initState` with no matching release in
|
|
77
|
+
`dispose()`.
|
|
78
|
+
- `BuildContext` or `setState()` used after an `await` with no
|
|
79
|
+
`context.mounted`/`mounted` check immediately before that use.
|
|
80
|
+
- Business logic (network calls, parsing, validation) written directly
|
|
81
|
+
inside a `build()` method instead of a controller/view-model/state
|
|
82
|
+
object the widget merely reads from — `build()` can be called many
|
|
83
|
+
times for reasons unrelated to data changing, so anything with a side
|
|
84
|
+
effect or real cost does not belong there.
|
|
85
|
+
- Mixing state-management approaches in the same feature (e.g. a
|
|
86
|
+
`Riverpod` provider read via `ref` next to an ad hoc
|
|
87
|
+
`InheritedWidget`/`setState` for the same piece of state) instead of
|
|
88
|
+
following whichever one the project has already standardized on.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.dart"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Flutter/Dart security
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic security rules to Flutter/Dart-
|
|
11
|
+
specific risks and the safe APIs to use instead. Applies only to `*.dart`
|
|
12
|
+
files.
|
|
13
|
+
|
|
14
|
+
## Secret and credential storage
|
|
15
|
+
|
|
16
|
+
- Store a token, session cookie, refresh token, or any other credential in
|
|
17
|
+
`flutter_secure_storage` (backed by Keychain on iOS, the Android
|
|
18
|
+
Keystore on Android — check the plugin's current backing implementation
|
|
19
|
+
rather than assuming `EncryptedSharedPreferences`, which has known
|
|
20
|
+
reliability issues and is no longer recommended by Google for new
|
|
21
|
+
Android code), never in `shared_preferences` — `SharedPreferences`
|
|
22
|
+
persists as an unencrypted plist/XML file on disk that any process with
|
|
23
|
+
filesystem access (a rooted device, a backup extraction) can read
|
|
24
|
+
directly.
|
|
25
|
+
- `shared_preferences` remains fine for genuinely non-sensitive
|
|
26
|
+
preferences (a theme choice, a "seen onboarding" flag) — the rule is
|
|
27
|
+
about what the value *is*, not a blanket ban on the package.
|
|
28
|
+
- Never hard-code an API key, client secret, or signing credential as a
|
|
29
|
+
Dart string literal compiled into the app — every string in a release
|
|
30
|
+
build is recoverable from the compiled binary with straightforward
|
|
31
|
+
reverse-engineering tools. `--dart-define`/a build-time `.env` file is
|
|
32
|
+
NOT a way to keep a secret server-side — both are compiled into the
|
|
33
|
+
binary at build time exactly like a string literal and are just as
|
|
34
|
+
recoverable from the shipped artifact; they only avoid committing the
|
|
35
|
+
secret to source control, which is a different property. For a value
|
|
36
|
+
that must actually stay off the device, fetch it at runtime from a
|
|
37
|
+
backend-issued short-lived token or a secrets manager reached over an
|
|
38
|
+
authenticated network call — never bake it into the build at all.
|
|
39
|
+
|
|
40
|
+
## Network calls and certificate pinning
|
|
41
|
+
|
|
42
|
+
- Use `https://` for every network call that carries user data,
|
|
43
|
+
credentials, or tokens; a plaintext `http://` endpoint is trivially
|
|
44
|
+
interceptable on a shared or compromised network.
|
|
45
|
+
- For a call carrying especially sensitive data (payment, health,
|
|
46
|
+
authentication) where the project has decided the risk of a
|
|
47
|
+
compromised/misissued CA in the platform trust store is worth guarding
|
|
48
|
+
against, use certificate pinning (`HttpClient.badCertificateCallback`
|
|
49
|
+
validated against a known pin, or a package such as
|
|
50
|
+
`http_certificate_pinning`/platform-native pinning) — but treat this as
|
|
51
|
+
a deliberate, project-level decision with a rotation plan, not a default
|
|
52
|
+
to apply to every request, since a mishandled pin update can lock out
|
|
53
|
+
legitimate traffic when a certificate rotates.
|
|
54
|
+
- Never override `badCertificateCallback` (or an equivalent HTTP client
|
|
55
|
+
hook) to unconditionally return `true` — that disables certificate
|
|
56
|
+
validation entirely for that client, the same failure class as Go's
|
|
57
|
+
`InsecureSkipVerify: true`, and is never appropriate outside a
|
|
58
|
+
short-lived local test.
|
|
59
|
+
|
|
60
|
+
## Platform channel and input validation
|
|
61
|
+
|
|
62
|
+
- Validate and sanitize any value crossing a `MethodChannel`/
|
|
63
|
+
`EventChannel` in either direction — a payload from native code is
|
|
64
|
+
external input from the Dart side's perspective, and a payload from
|
|
65
|
+
Dart is external input from the native side's perspective; do not trust
|
|
66
|
+
a channel argument's shape or range without checking it, especially
|
|
67
|
+
before using it to index a collection, build a file path, or construct
|
|
68
|
+
a query.
|
|
69
|
+
- Encode structured data crossing a platform channel with the codec the
|
|
70
|
+
channel already uses (the default standard codec, or an explicit
|
|
71
|
+
`MessageCodec`) rather than hand-rolling string concatenation/parsing
|
|
72
|
+
across the channel boundary, which reintroduces the same injection and
|
|
73
|
+
parsing-ambiguity risks as building a query string by hand.
|
|
74
|
+
|
|
75
|
+
## Local storage and logging
|
|
76
|
+
|
|
77
|
+
- Never write a secret, token, or PII value to `debugPrint`/`print` or any
|
|
78
|
+
logging sink that a release build still ships with enabled — a log
|
|
79
|
+
statement written for local debugging has a way of surviving into a
|
|
80
|
+
shipped build and into crash-reporting payloads.
|
|
81
|
+
- A `WebView` rendering any content that includes user-controlled or
|
|
82
|
+
externally-fetched HTML must not run with JavaScript enabled on
|
|
83
|
+
untrusted content unless the content is sanitized first — the same XSS
|
|
84
|
+
risk class as an unescaped web template, just inside a native app.
|
|
85
|
+
|
|
86
|
+
## Dependency hygiene
|
|
87
|
+
|
|
88
|
+
- Run `flutter pub outdated` and review a dependency's changelog/CVE
|
|
89
|
+
history before a major-version bump, and prefer `dart pub deps` to
|
|
90
|
+
understand what a new transitive dependency actually pulls in before
|
|
91
|
+
adding it.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.dart"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Flutter/Dart testing
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic testing rules to
|
|
11
|
+
`flutter_test`'s widget-test framework and `package:test`'s unit-test
|
|
12
|
+
conventions. Applies only to `*.dart` files.
|
|
13
|
+
|
|
14
|
+
## Layout and naming
|
|
15
|
+
|
|
16
|
+
- Unit and widget tests live under `test/`, mirroring `lib/`'s structure
|
|
17
|
+
(`lib/order/order_service.dart` → `test/order/order_service_test.dart`)
|
|
18
|
+
— no separate ad hoc fixture tree.
|
|
19
|
+
- A widget test file's `testWidgets('<behavior it verifies>', (tester)
|
|
20
|
+
async { ... })` description names the observable behavior under test,
|
|
21
|
+
not the widget's implementation detail — `'shows an error banner when
|
|
22
|
+
submit fails'`, not `'calls setState'`.
|
|
23
|
+
- `group('OrderService', () { ... })` to cluster related cases for one
|
|
24
|
+
class/widget, matching whatever the project's existing suite already
|
|
25
|
+
does.
|
|
26
|
+
|
|
27
|
+
## Widget tests
|
|
28
|
+
|
|
29
|
+
- Build the widget under test with `tester.pumpWidget(...)`, wrapped in
|
|
30
|
+
whatever ancestor widgets it actually needs to render (a `MaterialApp`/
|
|
31
|
+
`CupertinoApp`, a `Localizations` scope, a state-management provider) —
|
|
32
|
+
matching the app's real widget tree shape, not an arbitrary bare
|
|
33
|
+
wrapper.
|
|
34
|
+
- Locate widgets with `find.byType`, `find.text`, `find.byKey`, or
|
|
35
|
+
`find.byIcon` — prefer `find.byKey` for a widget identified by role
|
|
36
|
+
rather than by its current text/type when either could plausibly change
|
|
37
|
+
without the behavior changing.
|
|
38
|
+
- Use `tester.pump()` to advance exactly one frame (a single `setState`
|
|
39
|
+
or a fixed-duration animation step) and `tester.pumpAndSettle()` when
|
|
40
|
+
waiting for an animation, page transition, or async operation to fully
|
|
41
|
+
finish before asserting — but do not reach for `pumpAndSettle()` by
|
|
42
|
+
default for everything: a widget under a `Timer.periodic` or another
|
|
43
|
+
never-ending animation makes `pumpAndSettle()` time out waiting for a
|
|
44
|
+
frame that never stops changing, so a bounded `pump(Duration(...))` is
|
|
45
|
+
the correct tool there instead.
|
|
46
|
+
- Interact with `tester.tap(finder)`, `tester.enterText(finder, '...')`,
|
|
47
|
+
`tester.drag(finder, offset)`, each followed by a `pump()`/
|
|
48
|
+
`pumpAndSettle()` appropriate to what the interaction triggers, before
|
|
49
|
+
asserting on the result.
|
|
50
|
+
|
|
51
|
+
## Mocking the boundary, not the internals
|
|
52
|
+
|
|
53
|
+
- Mock at the external boundary a widget/class depends on — a repository,
|
|
54
|
+
an HTTP client, a platform-channel wrapper — defined as an interface (an
|
|
55
|
+
abstract class) the real implementation and a test double both satisfy,
|
|
56
|
+
using `mocktail` (no code generation, works with sound null safety) or
|
|
57
|
+
`mockito` (code-generation based) to build that double.
|
|
58
|
+
- Never mock a class's own internal collaborator that exists purely to
|
|
59
|
+
implement its behavior (a private helper, a widget's own internal
|
|
60
|
+
state) — mocking one layer too deep tests that the implementation calls
|
|
61
|
+
itself in a particular way, not that the widget/class actually produces
|
|
62
|
+
the right externally-observable behavior, and it breaks on every
|
|
63
|
+
internal refactor even when the behavior is unchanged.
|
|
64
|
+
- Verify a mocked dependency's interaction (`verify(() =>
|
|
65
|
+
mockRepository.fetchOrders()).called(1)`) only for calls that matter to
|
|
66
|
+
the behavior under test — do not over-verify every incidental call,
|
|
67
|
+
which turns the test into a change-detector for implementation details.
|
|
68
|
+
|
|
69
|
+
## Async state in widget tests
|
|
70
|
+
|
|
71
|
+
- When a widget's state depends on a `Future` (a repository call behind a
|
|
72
|
+
loading spinner), pump through the states explicitly: `pump()` once to
|
|
73
|
+
render the loading state, then `pump()`/`pumpAndSettle()` again after
|
|
74
|
+
the mocked `Future` resolves, asserting the loading indicator is gone
|
|
75
|
+
and the real content is present — asserting only on the state
|
|
76
|
+
immediately after `pumpWidget()` misses the loading-to-loaded
|
|
77
|
+
transition entirely.
|
|
78
|
+
- Never use `Future.delayed`/a real `Duration` wait inside a test to give
|
|
79
|
+
a mocked async call "enough time" to resolve — control the mock's
|
|
80
|
+
`Future` directly (a `Completer` the test completes explicitly, or a
|
|
81
|
+
mock configured to resolve immediately) and drive time forward with
|
|
82
|
+
`pump()`/`pumpAndSettle()`/`tester.pump(duration)` instead, so the test
|
|
83
|
+
is deterministic rather than timing-dependent.
|
|
84
|
+
|
|
85
|
+
## Fixtures and golden tests
|
|
86
|
+
|
|
87
|
+
- `testWidgets` fixtures (sample model instances, mock repository
|
|
88
|
+
responses) live in a `test/fixtures/` or per-feature helper file,
|
|
89
|
+
matching whatever the project already uses — do not invent a second
|
|
90
|
+
fixture convention alongside an existing one.
|
|
91
|
+
- A golden-image test (`matchesGoldenFile`) is opt-in per project; only
|
|
92
|
+
add one where the project already has a golden-test setup and baseline
|
|
93
|
+
images checked in, and regenerate baselines deliberately (`flutter test
|
|
94
|
+
--update-goldens`), not as a side effect of an unrelated change.
|
|
95
|
+
|
|
96
|
+
## Running and coverage
|
|
97
|
+
|
|
98
|
+
- Run `flutter test` (or `flutter test --coverage` when the project
|
|
99
|
+
tracks coverage) before reporting a testing task done; fix a failing
|
|
100
|
+
test by correcting the test or the fixture it depends on, not by
|
|
101
|
+
weakening its `expect`/`find` assertion to make it pass.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flutter-build-fix
|
|
3
|
+
description: "Use when flutter analyze/flutter build fails, or pubspec.yaml/pubspec.lock are out of sync -- resolves dependency version conflicts, analyzer/lint failures, null-safety compile errors, and a failing flutter test, with the smallest root-cause fix."
|
|
4
|
+
triggers:
|
|
5
|
+
- "flutter analyze is failing"
|
|
6
|
+
- "flutter build is failing with a dependency error"
|
|
7
|
+
- "fix this pubspec.yaml version conflict"
|
|
8
|
+
- "resolve this Dart null-safety compile error"
|
|
9
|
+
- "this Flutter lint is failing in CI"
|
|
10
|
+
- "flutter test is failing after a pub upgrade"
|
|
11
|
+
metadata:
|
|
12
|
+
origin: authored
|
|
13
|
+
category: build-fix
|
|
14
|
+
version: "1.0.0"
|
|
15
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
16
|
+
license: "MIT"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Flutter/Dart build fix
|
|
20
|
+
|
|
21
|
+
Resolve a `flutter analyze`/`flutter build` failure, a `pubspec.yaml`/
|
|
22
|
+
`pubspec.lock` mismatch, a null-safety compile error, an analyzer/lint
|
|
23
|
+
failure, or a failing `flutter test` — with the smallest change that fixes
|
|
24
|
+
the actual root cause. `rules/coding-style.mdc` and `rules/security.mdc`
|
|
25
|
+
govern what a "correct" fix looks like; this skill never reaches for a
|
|
26
|
+
suppression instead of a fix.
|
|
27
|
+
|
|
28
|
+
## Workflow
|
|
29
|
+
|
|
30
|
+
### Step 1: Reproduce and classify
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
flutter analyze
|
|
34
|
+
dart format --set-exit-if-changed .
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Read the exact error text and classify it:
|
|
38
|
+
|
|
39
|
+
- **Compile error** (undefined identifier, type mismatch, missing
|
|
40
|
+
override).
|
|
41
|
+
- **Null-safety error** (a nullable value used where non-null is
|
|
42
|
+
required, a missing `late`/`required`, an unhandled `null` case).
|
|
43
|
+
- **Dependency/version conflict** (`pubspec.yaml`/`pubspec.lock`
|
|
44
|
+
mismatch, `flutter pub get` reporting a version solving failure).
|
|
45
|
+
- **Analyzer/lint finding** (`flutter analyze`'s own rules, or a rule from
|
|
46
|
+
the project's `analysis_options.yaml`).
|
|
47
|
+
- **Failing test** (`flutter test` reports a failed `expect`/`find`
|
|
48
|
+
assertion, or a widget test exception).
|
|
49
|
+
|
|
50
|
+
### Step 2: Fix by category
|
|
51
|
+
|
|
52
|
+
**Dependency/version conflict:** run `flutter pub get` (or `flutter pub
|
|
53
|
+
upgrade` when a newer compatible version is needed) and read the actual
|
|
54
|
+
version-solving error — it names which packages conflict and why. Check
|
|
55
|
+
`flutter pub deps` to see the dependency graph before pinning a version by
|
|
56
|
+
hand. Only add a `dependency_overrides` entry when it is a real,
|
|
57
|
+
intentional override (a known-good pre-release, a local path dependency
|
|
58
|
+
during development) — never to silently paper over a conflict without
|
|
59
|
+
understanding it, and say so in the report either way.
|
|
60
|
+
|
|
61
|
+
**Null-safety compile error:** fix the actual nullability gap — add a
|
|
62
|
+
null check, use `?.`/`??`, make a constructor parameter `required` or give
|
|
63
|
+
it a default, or correct a type that should not have been nullable in the
|
|
64
|
+
first place. Never add `!` purely to make the compiler stop complaining
|
|
65
|
+
without confirming the value is actually non-null at that point.
|
|
66
|
+
|
|
67
|
+
**Analyzer/lint finding:** fix the underlying issue the finding names
|
|
68
|
+
(the real missing override, the actual unused import, the genuine dead
|
|
69
|
+
code). Never add `// ignore: <rule>` or a blanket `// ignore_for_file:`
|
|
70
|
+
comment whose only purpose is to make the analyzer stop complaining
|
|
71
|
+
without addressing what it found.
|
|
72
|
+
|
|
73
|
+
**Failing test:** read the assertion failure and fix the actual cause —
|
|
74
|
+
either the implementation has a real bug the test correctly caught (fix
|
|
75
|
+
the implementation, say so), or the test/fixture is stale (fix the test).
|
|
76
|
+
Never delete or weaken a `find`/`expect` assertion just to reach green.
|
|
77
|
+
|
|
78
|
+
### Step 3: Verify
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
flutter analyze
|
|
82
|
+
dart format --set-exit-if-changed .
|
|
83
|
+
flutter test
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Re-run `flutter build <platform>` if the original failure was a build (not
|
|
87
|
+
just an analyze/test) failure. All must exit 0 before reporting done.
|
|
88
|
+
|
|
89
|
+
### Step 4: Report
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
Fixed: pubspec.yaml/pubspec.lock version conflict (ran `flutter pub get`
|
|
93
|
+
after loosening a pinned transitive constraint)
|
|
94
|
+
- Root cause: pubspec.lock predated a direct dependency bump in
|
|
95
|
+
pubspec.yaml
|
|
96
|
+
- flutter analyze / dart format / flutter test all pass
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
State the root cause in one sentence, not just "fixed the error."
|
|
100
|
+
|
|
101
|
+
## Rules
|
|
102
|
+
|
|
103
|
+
- Find and fix the smallest change that addresses the actual root cause —
|
|
104
|
+
never widen a fix beyond what the failure requires.
|
|
105
|
+
- NEVER add `// ignore:` or `// ignore_for_file:` to silence an analyzer
|
|
106
|
+
finding instead of fixing what it found.
|
|
107
|
+
- NEVER add the bang operator (`!`) to a nullable value just to make a
|
|
108
|
+
compile error disappear without confirming non-nullability.
|
|
109
|
+
- NEVER add a `dependency_overrides` entry to route around a real version
|
|
110
|
+
conflict without confirming it is an intentional, documented override.
|
|
111
|
+
- NEVER delete or weaken a failing test's assertion to reach a green
|
|
112
|
+
build.
|
|
113
|
+
|
|
114
|
+
## Red Flags
|
|
115
|
+
|
|
116
|
+
| Rationalization | Why it is wrong |
|
|
117
|
+
|---|---|
|
|
118
|
+
| "I'll add `// ignore: prefer_const_constructors` here so analyze passes" | Silences the finding without fixing the actual missed `const`, which is exactly the performance signal the lint exists to catch |
|
|
119
|
+
| "I'll just add `!` here so the compiler stops complaining about this nullable value" | Papers over a real null-safety gap the compiler correctly found; add a null check or `?.`/`??` instead of asserting past it |
|
|
120
|
+
| "This dependency conflict is annoying, I'll add a dependency_override to force the version I want" | Routes around a real incompatibility the version solver found without understanding why it conflicts; check `flutter pub deps` and resolve the actual conflict first |
|
|
121
|
+
| "This widget test is flaky, I'll just remove the assertion that's failing" | Hides a real bug or a genuinely broken test instead of fixing either one; read the failure and fix the actual cause |
|
|
122
|
+
|
|
123
|
+
## Verification
|
|
124
|
+
|
|
125
|
+
Do not report the fix done until all of the following hold:
|
|
126
|
+
|
|
127
|
+
- `flutter analyze`, `dart format --set-exit-if-changed .`, and `flutter
|
|
128
|
+
test` all exit 0.
|
|
129
|
+
- `flutter build <platform>` exits 0 if the original failure was a build
|
|
130
|
+
failure.
|
|
131
|
+
- The change is the smallest one that addresses the stated root cause —
|
|
132
|
+
no unrelated files touched.
|
|
133
|
+
- The report states the root cause in one sentence, not just "build now
|
|
134
|
+
passes."
|