From cece013d842dc174b1a8be6c05d08f560371fe11 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Mon, 27 Apr 2026 09:53:11 -0400 Subject: [PATCH] Add new kevmoo dart skills and grill me skill for feature planning, update existing skills --- .../skills/dart-best-practices/SKILL.md | 11 + .../skills/dart-checks-migration/SKILL.md | 158 ++++++++ .../dart-cli-app-best-practices/SKILL.md | 46 ++- .../skills/dart-doc-validation/SKILL.md | 42 ++ .../.agents/skills/dart-long-lines/SKILL.md | 101 +++++ .../dart-matcher-best-practices/SKILL.md | 24 +- .../skills/dart-modern-features/SKILL.md | 27 +- .../skills/dart-package-maintenance/SKILL.md | 17 + .../skills/dart-test-coverage/SKILL.md | 92 +++++ .../example/lib/src/calculator.dart | 7 + .../dart-test-coverage/example/pubspec.lock | 381 ++++++++++++++++++ .../dart-test-coverage/example/pubspec.yaml | 8 + .../example/test/calculator_test.dart | 11 + .../scripts/interpret_coverage.dart | 82 ++++ .../skills/dart-test-fundamentals/SKILL.md | 58 ++- .../.agents/skills/grill-me/SKILL.md | 10 + tool/dart_skills_lint/skills-lock.json | 34 +- 17 files changed, 1087 insertions(+), 22 deletions(-) create mode 100644 tool/dart_skills_lint/.agents/skills/dart-checks-migration/SKILL.md create mode 100644 tool/dart_skills_lint/.agents/skills/dart-long-lines/SKILL.md create mode 100644 tool/dart_skills_lint/.agents/skills/dart-test-coverage/SKILL.md create mode 100644 tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/lib/src/calculator.dart create mode 100644 tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/pubspec.lock create mode 100644 tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/pubspec.yaml create mode 100644 tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/test/calculator_test.dart create mode 100644 tool/dart_skills_lint/.agents/skills/dart-test-coverage/scripts/interpret_coverage.dart create mode 100644 tool/dart_skills_lint/.agents/skills/grill-me/SKILL.md diff --git a/tool/dart_skills_lint/.agents/skills/dart-best-practices/SKILL.md b/tool/dart_skills_lint/.agents/skills/dart-best-practices/SKILL.md index cfe2c41a..690cce7e 100644 --- a/tool/dart_skills_lint/.agents/skills/dart-best-practices/SKILL.md +++ b/tool/dart_skills_lint/.agents/skills/dart-best-practices/SKILL.md @@ -45,6 +45,17 @@ without horizontal scrolling. Target 80 characters for wrapping text. Exceptions are allowed for long URLs or identifiers that cannot be broken. +## Discovery + +### Multi-line Strings +To find candidates for multi-line strings, search for string concatenation +with `+` involving newlines: +- **Regex**: `['"]\s*\+\s*['"]` +- **Regex**: `\+\s*['"].*\\n` + +### Line Length +- Rely on the `lines_longer_than_80_chars` lint from the analyzer. + ## Related Skills - **[dart-modern-features]**: For idiomatic diff --git a/tool/dart_skills_lint/.agents/skills/dart-checks-migration/SKILL.md b/tool/dart_skills_lint/.agents/skills/dart-checks-migration/SKILL.md new file mode 100644 index 00000000..df5036ba --- /dev/null +++ b/tool/dart_skills_lint/.agents/skills/dart-checks-migration/SKILL.md @@ -0,0 +1,158 @@ +--- +name: dart-checks-migration +description: |- + Replace the usage of `expect` and similar functions from `package:matcher` + to `package:checks` equivalents. +license: Apache-2.0 +--- + +# Dart Checks Migration + +## When to use this skill +Use this skill when: +- Migrating existing test files from `package:matcher` to `package:checks`. +- A user specifically asks for "modern checks" or similar. + +## The Workflow + +1. **Analysis**: + - Use `grep` to identify files using `expect` or `package:matcher`. + - Review custom matchers; these may require manual migration. +2. **Tools & Dependencies**: + - Ensure `dev_dependencies` includes `checks`. + - Run `dart pub add --dev checks` if missing. +3. **Discovery**: + - Use the **Strategies for Discovery** below to find candidates. +4. **Replacement**: + - Add `import 'package:checks/checks.dart';`. + - Apply the **Common Patterns** below. + - **Final Step**: Replace `import 'package:test/test.dart';` with + `import 'package:test/scaffolding.dart';` ONLY after all `expect` calls + are replaced. This ensures incremental progress. +5. **Verification**: + - Ensure the code analyzes cleanly. + - Ensure tests pass. + +## Strategies for Discovery + +To find candidates for migration, use the following search strategies: + +### Files using Legacy Matchers +Search for test files that import `package:test/test.dart` or use `expect`: +- **Search Query**: `import 'package:test/test.dart';` +- **Regex**: `expect\(` + +### Specific Matchers to Target +Search for specific matchers that are easy to migrate: +- **Regex**: `expect\(.*,\s*equals\(` +- **Regex**: `expect\(.*,\s*isNull\)` +- **Regex**: `expect\(.*,\s*isTrue\)` +- **Regex**: `expect\(.*,\s*isFalse\)` +- **Regex**: `expect\(.*,\s*throwsA` + +## Common Patterns + +| Legacy `expect` | Modern `check` | +| :--- | :--- | +| `expect(a, equals(b))` | `check(a).equals(b)` | +| `expect(a, isTrue)` | `check(a).isTrue()` | +| `expect(a, isFalse)` | `check(a).isFalse()` | +| `expect(a, isNull)` | `check(a).isNull()` | +| `expect(a, isNotNull)` | `check(a).isNotNull()` | +| `expect(() => fn(), throwsA())` | `check(() => fn()).throws()` | +| `expect(list, hasLength(n))` | `check(list).length.equals(n)` | +| `expect(a, closeTo(b, delta))` | `check(a).isA().isCloseTo(b, delta)` | +| `expect(a, greaterThan(b))` | `check(a).isGreaterThan(b)` | +| `expect(a, lessThan(b))` | `check(a).isLessThan(b)` | +| `expect(list, isEmpty)` | `check(list).isEmpty()` | +| `expect(list, isNotEmpty)` | `check(list).isNotEmpty()` | +| `expect(list, contains(item))` | `check(list).contains(item)` | +| `expect(map, equals(otherMap))` | `check(map).deepEquals(otherMap)` | +| `expect(list, equals(otherList))` | `check(list).deepEquals(otherList)` | +| `expect(future, completes)` | `await check(future).completes()` | +| `expect(stream, emitsInOrder(...))` | `await check(stream).withQueue.inOrder(...)` | + +### Async & Futures (CRITICAL) + +- **Checking async functions:** + `check(() => asyncFunc()).throws()` causes **FALSE POSITIVES** because the + closure returns a `Future`, which is a value, so it "completes normally" + (as a Future). + **Correct Usage:** + ```dart + await check(asyncFunc()).throws(); + ``` + +- **Chaining on void returns:** + Many async check methods (like `throws`) return `Future`. You cannot + chain directly on them. Use cascades or callbacks. + **Wrong:** + ```dart + await check(future) + .throws() + .has((e) => e.message, 'message') + .equals('foo'); + ``` + **Correct:** + ```dart + await check(future).throws( + (it) => it.has((e) => e.message, 'message').equals('foo')); + ``` + +## Complex Examples + +*Deep Verification with `isA` and `having`:* + +**Legacy:** +```dart +expect(() => foo(), throwsA(isA() + .having((e) => e.message, 'message', contains('MSG')))); +``` + +**Modern:** +```dart +check(() => foo()) + .throws() + .has((e) => e.message, 'message') + .contains('MSG'); +``` + +*Property Extraction:* + +**Legacy:** +```dart +expect(obj.prop, equals(value)); // When checking multiple props +``` + +**Modern:** +```dart +check(obj) + ..has((e) => e.prop, 'prop').equals(value) + ..has((e) => e.other, 'other').equals(otherValue); +``` + +*One-line Cascades:* +Since checks often return `void`, use cascades for multiple assertions on the +same subject. +```dart +check(it)..isGreaterThan(10)..isLessThan(20); +``` + +## Constraints + +- **Scope**: Only modify files in `test/` (and `pubspec.yaml`). +- **Correctness**: One failing test is unacceptable. If a test fails after + migration and you cannot fix it immediately, REVERT that specific change. +- **Type Safety**: `package:checks` is stricter about types than `matcher`. + You may need to add explicit `as T` casts or `isA()` checks in the chain. + +## Related Skills + +- **[dart-test-fundamentals]**: Core + concepts for structuring tests, lifecycles, and configuration. +- **[dart-matcher-best-practices]**: + Best practices for the traditional `package:matcher` that is being migrated + away from. + +[dart-test-fundamentals]: https://github.com/kevmoo/dash_skills/blob/main/.agent/skills/dart-test-fundamentals/SKILL.md +[dart-matcher-best-practices]: https://github.com/kevmoo/dash_skills/blob/main/.agent/skills/dart-matcher-best-practices/SKILL.md diff --git a/tool/dart_skills_lint/.agents/skills/dart-cli-app-best-practices/SKILL.md b/tool/dart_skills_lint/.agents/skills/dart-cli-app-best-practices/SKILL.md index 22517336..578b76b0 100644 --- a/tool/dart_skills_lint/.agents/skills/dart-cli-app-best-practices/SKILL.md +++ b/tool/dart_skills_lint/.agents/skills/dart-cli-app-best-practices/SKILL.md @@ -35,7 +35,27 @@ Future main(List arguments) async { - Put complex logic directly in `bin/my_app.dart`. - Define classes or heavy functions in the entrypoint. -### Executable Scripts +### Put an `executable` entry in `pubspec.yaml` + +List your executables in `pubspec.yaml` to make them available for global +activation and clean invocation via `dart run`. + +**DO:** +Add an `executables` section mapping the command name to the Dart file in +`bin/` (excluding the `.dart` extension). + +```yaml +executables: + my_app: # Maps to bin/my_app.dart + custom_name: main # Maps to bin/main.dart +``` + +Then run via `dart run my_app` or `dart run custom_name`. + +### CONSIDER `#!` for other scripts on *nix systems + +This is NOT a hard and fast rule, but it is something to consider. + For CLI tools intended to be run directly on Linux and Mac, add a shebang and ensure the file is executable. @@ -95,12 +115,28 @@ Future main(List arguments) async { ### Cross-Platform Compatibility (Windows Support) When writing CLI applications and tests, ensure compatibility with Windows: -- **Paths**: Never hardcode path separators like `/`. Use `package:path`'s - `p.join` or `p.normalize` to construct paths portably. +- **Paths**: Avoid hardcoding path separators like `/` because Windows uses `\`. + Use `package:path`'s `p.join` or `p.normalize` to construct paths portably. - **File Permissions**: When testing file permission errors, remember that `chmod` is not available on Windows. Use `icacls` on Windows or appropriate - mock libraries. Never skip tests on Windows simply because of permission - commands if a Windows equivalent exists. + mock libraries to simulate permission errors. Never skip tests on Windows + simply because of permission commands if a Windows equivalent exists. + +## Discovery + +To find areas to apply these best practices: + +### Heavy Entrypoints +Inspect files in `bin/` to see if they contain logic that should be in `lib/`: +- **Target**: Files matching `bin/*.dart`. + +### Direct Process Termination +Search for calls to `exit()` instead of setting `exitCode`: +- **Regex**: `\bexit\(` + +### Hardcoded Path Separators +Search for hardcoded `/` in strings that appear to be file paths: +- **Regex**: `['"][^'"]+/[^'"]+['"]` (Verify context as this may match URLs). ## 3. Recommended Packages diff --git a/tool/dart_skills_lint/.agents/skills/dart-doc-validation/SKILL.md b/tool/dart_skills_lint/.agents/skills/dart-doc-validation/SKILL.md index 4800659a..d317e440 100644 --- a/tool/dart_skills_lint/.agents/skills/dart-doc-validation/SKILL.md +++ b/tool/dart_skills_lint/.agents/skills/dart-doc-validation/SKILL.md @@ -15,8 +15,33 @@ Use this skill when: - Checking for broken documentation links, references, or macros. - Preparing a package for publishing to pub.dev. +## Discovery + +To find documentation issues: + +### Missing Lint +Verify if the `comment_references` lint is enabled: +- **Target**: `analysis_options.yaml` +- **Search Query**: `comment_references` + +### Automated Validation +Run the documentation generator to surface warnings: +- **Command**: `dart doc -o $(mktemp -d)` +- **Keywords to look for**: `warning:`, `unresolved doc reference`, + `undefined macro` + ## 2. Best Practices +### Enable the doc validation lint + +In your `analysis_options.yaml`, enable the `comment_references` lint. + +```yaml +linter: + rules: + - comment_references +``` + ### Validating Documentation Locally Use the `dart doc` command with a temporary output directory to validate @@ -27,14 +52,31 @@ This command parses all documentation comments and reports warnings such as: - `warning: undefined macro` **Command to run:** + ```bash dart doc -o $(mktemp -d) ``` +*This will work on Mac and Linux.* + This ensures that the generated HTML files are stored in a temporary location and don't clutter the package directory, while still surfacing all validation warnings in the terminal output. +**Browsing the docs:** + +Our docs use features designed to be run on a web server. If you want to browse +the generated docs locally, install the `dhttpd` package. + + +```shell +pub global activate dhttpd +TMP_DIR=$(mktemp -d) && dart doc -o "$TMP_DIR" && dhttpd --path "$TMP_DIR" +``` + +*(Or use another HTTP server, such as `python3 -m http.server`.)* + + ### Fixing Common Warnings - **Unresolved doc reference**: Ensure that any identifier wrapped in square diff --git a/tool/dart_skills_lint/.agents/skills/dart-long-lines/SKILL.md b/tool/dart_skills_lint/.agents/skills/dart-long-lines/SKILL.md new file mode 100644 index 00000000..e889e597 --- /dev/null +++ b/tool/dart_skills_lint/.agents/skills/dart-long-lines/SKILL.md @@ -0,0 +1,101 @@ +--- +name: dart-long-lines +description: |- + Guidelines for handling long lines in Dart code to adhere to the 80-column + rule. The `lines_longer_than_80_chars` lint. +license: Apache-2.0 +--- + +# Dart Long Lines + +## 1. When to use this skill + +Use this skill when: +- Writing Dart code that might exceed the 80-column limit. +- Refactoring code to comply with the `lines_longer_than_80_chars` lint. + Reference: https://dart.dev/tools/linter-rules/lines_longer_than_80_chars + +## Discovery + +To find lines that exceed the limit: + +### Automated Analysis +The most reliable way to find long lines is to use the Dart analyzer: +- **Command**: `dart analyze` +- **Lint**: `lines_longer_than_80_chars` + +### Manual Search +To search for long lines using regex: +- **Regex**: `^.{81,}$` (Matches any line with 81 or more characters). + +## 2. Guidelines + +### Format First +Always run `dart format` before manually breaking long lines. The formatter +often automatically fixes long lines, especially in generated code, and +applies standard Dart styling rules. + +### Code Comments +Break long code comments (`//`) cleanly at word boundaries to ensure lines do +not exceed 80 characters. Maintain tight formatting and avoid unnecessary +vertical space. + +### Documentation Comments (`///`) +- Apply the same line-breaking rules as for code comments. +- Avoid breaking markdown link blocks like `[name]` or + `[text](http://example.com)` across lines. Place them on their own line if + they exceed the limit. +- Start doc comments with a single summary sentence, followed by a blank line + before the rest of the comment. It is okay to break this first sentence + across multiple lines to fit the 80-column limit. +- Avoid unresolved references or dangling sentences. + +### Long Strings +- Use adjacent string literals (e.g., `'part 1 ' 'part 2'`) to break long + strings. Break at word boundaries. +- If a single-line string contains newline characters (`\n`) or if there are + consecutive `print` statements, consider migrating to a multi-line string + literal (`'''`). + +### Format and Analyze After Changes +- Run `dart format` and `dart analyze` after making changes. +- Be aware that splitting strings may trigger new lints (e.g., + `prefer_single_quotes` if a double-quoted string is split into parts that + no longer contain single quotes). + +## 3. Examples + +### Documentation Comment Link +**Avoid:** +```dart +/// This is a long doc comment that contains a link to [a very long +/// URL](http://example.com/very/long/url/that/exceeds/eighty/chars). +``` + +**Prefer:** +```dart +/// This is a long doc comment that contains a link to [a very long URL][ref]. +/// +/// [ref]: http://example.com/very/long/url/that/exceeds/eighty/chars +``` + +### Adjacent String Literals +**Prefer:** +```dart +final longString = 'This is a very long string that needs to be broken ' + 'across multiple lines to stay under the limit.'; +``` + +### Multi-line String Migration +**Avoid:** +```dart +print('This is line 1\nThis is line 2 that is also quite long\nThis is line 3 which makes the whole thing exceed eighty characters'); +``` + +**Prefer:** +```dart +print('''This is line 1 +This is line 2 that is also quite long +This is line 3 which makes the whole thing exceed eighty characters'''); +``` + diff --git a/tool/dart_skills_lint/.agents/skills/dart-matcher-best-practices/SKILL.md b/tool/dart_skills_lint/.agents/skills/dart-matcher-best-practices/SKILL.md index d4343382..20d3e558 100644 --- a/tool/dart_skills_lint/.agents/skills/dart-matcher-best-practices/SKILL.md +++ b/tool/dart_skills_lint/.agents/skills/dart-matcher-best-practices/SKILL.md @@ -2,18 +2,38 @@ name: dart-matcher-best-practices description: |- Best practices for using `expect` and `package:matcher`. - Focuses on readable assertions, proper matcher selection, and avoiding common pitfalls. + Focuses on readable assertions, proper matcher selection, and avoiding + common pitfalls. license: Apache-2.0 --- # Dart Matcher Best Practices ## When to use this skill + Use this skill when: - Writing assertions using `expect` and `package:matcher`. - Migrating legacy manual checks to cleaner matchers. - Debugging confusing test failures. +## Discovery + +To find candidates for improving matcher usage, search for suboptimal patterns: + +### Suboptimal Length Checks +Search for length checks that should use `hasLength`: +- **Regex**: `expect\([^,]+.length,\s*` + +### Suboptimal Boolean Checks +Search for checks on boolean properties that have specific matchers: +- **Regex**: `expect\([^,]+.isEmpty,\s*(true|equals\(true\))` +- **Regex**: `expect\([^,]+.isNotEmpty,\s*(true|equals\(true\))` +- **Regex**: `expect\([^,]+.contains\(.*\),\s*(true|equals\(true\))` + +### Suboptimal Map Lookups +Search for manual map lookups instead of `containsPair`: +- **Regex**: `expect\([^,]+\[.*\],\s*` + ## Core Matchers ### 1. Collections (`hasLength`, `contains`, `isEmpty`, `unorderedEquals`, `containsPair`) @@ -36,7 +56,7 @@ Use this skill when: - **`containsPair(key, value)`**: - Verify a map contains a specific key-value pair. - - Prefer over checking `expect(map[key], value)` or + - Prefer over checking `expect(map[key], value)` or `expect(map.containsKey(key), true)`. ### 2. Type Checks (`isA` and `TypeMatcher`) diff --git a/tool/dart_skills_lint/.agents/skills/dart-modern-features/SKILL.md b/tool/dart_skills_lint/.agents/skills/dart-modern-features/SKILL.md index 1a1a68ff..a00c1c48 100644 --- a/tool/dart_skills_lint/.agents/skills/dart-modern-features/SKILL.md +++ b/tool/dart_skills_lint/.agents/skills/dart-modern-features/SKILL.md @@ -1,20 +1,43 @@ --- name: dart-modern-features description: |- - Guidelines for using modern Dart features (v3.0 - v3.10) such as Records, - Pattern Matching, Switch Expressions, Extension Types, Class Modifiers, + Guidelines for using modern Dart features (v3.0 - v3.10) such as Records, + Pattern Matching, Switch Expressions, Extension Types, Class Modifiers, Wildcards, Null-Aware Elements, and Dot Shorthands. --- # Dart Modern Features ## 1. When to use this skill + Use this skill when: - Writing or reviewing Dart code targeting Dart 3.0 or later. - Refactoring legacy Dart code to use modern, concise, and safe features. - Looking for idiomatic ways to handle multiple return values, deep data extraction, or exhaustive checking. +## Discovery + +To find candidates for modernization: + +### Switch Expressions +Search for switch statements where every case assigns to the same variable +or returns: +- **Regex**: `switch\s*\([^)]+\)\s*\{\s*case` + +### Pattern Matching Candidates +Search for manual map or JSON property extraction and type checking: +- **Regex**: `containsKey\(['"][^'"]+['"]\)` +- **Regex**: `json\[['"][^'"]+['"]\]\s+is\s+` + +### Null-Aware Elements +Search for collection `if` statements checking for null: +- **Regex**: `if\s*\(\w+\s*!=\s*null\)\s*\w+` + +### Digit Separators +Search for long numbers without separators: +- **Regex**: `\b\d{6,}\b` (Matches numbers with 6 or more digits). + ## 2. Features ### Records diff --git a/tool/dart_skills_lint/.agents/skills/dart-package-maintenance/SKILL.md b/tool/dart_skills_lint/.agents/skills/dart-package-maintenance/SKILL.md index a0f79421..a8309111 100644 --- a/tool/dart_skills_lint/.agents/skills/dart-package-maintenance/SKILL.md +++ b/tool/dart_skills_lint/.agents/skills/dart-package-maintenance/SKILL.md @@ -12,6 +12,15 @@ description: |- Guidelines for maintaining Dart packages in alignment with Dart team best practices. +## Discovery + +To find maintenance tasks or inconsistencies: + +### Consistency Checks +Ensure the latest version in `CHANGELOG.md` matches `pubspec.yaml`: +- Compare the top header in `CHANGELOG.md` with the `version:` field in + `pubspec.yaml`. + ## Versioning ### Semantic Versioning @@ -41,6 +50,14 @@ practices. (e.g., `1.1.0-wip`, `0.4.5-wip`). - **Bug Fixes**: Bump Patch (e.g., `1.0.1-wip`). +### Changelog Content +- **Focus on User Impact**: Entries in `CHANGELOG.md` should focus on changes + visible to or impacting the end-user (e.g., new features, bug fixes, + breaking changes). +- **Omit Internal Changes**: Do not include internal refactorings, test + changes, or other modifications that do not affect the package's behavior + or API for the user. + ### Work-in-Progress (WIP) Versions - Immediately after a publish, or on the first change after a publish, update `pubspec.yaml` and `CHANGELOG.md` with a `-wip` suffix (e.g., `1.1.0-wip`). diff --git a/tool/dart_skills_lint/.agents/skills/dart-test-coverage/SKILL.md b/tool/dart_skills_lint/.agents/skills/dart-test-coverage/SKILL.md new file mode 100644 index 00000000..c3e52405 --- /dev/null +++ b/tool/dart_skills_lint/.agents/skills/dart-test-coverage/SKILL.md @@ -0,0 +1,92 @@ +--- +name: dart-test-coverage +description: |- + Understand and improve test coverage in a Dart package. + Helps agents run coverage, interpret results, and identify missed lines. +--- + +# Dart Test Coverage + +Guidelines for running and interpreting test coverage in Dart packages. + +## When to use this skill +- When asked to "check test coverage" or "improve coverage". +- When you need to identify which parts of a library are untested. + +## Discovery + +To find areas lacking test coverage: + +### Run Coverage Analysis +Follow the workflow to generate and interpret coverage data: +1. **Run Tests with Coverage**: `dart test --coverage=.dart_tool/coverage` +2. **Interpret Results**: Use the script or `format_coverage` as described in + the **Interpreting Results** section to identify specific files and missed + lines. + +## How to use this skill (The Workflow) +1. Ensure tests pass by running `dart test`. +2. Collect coverage by running `dart test --coverage=.dart_tool/coverage`. +3. Interpret the results using the provided script or standard tools. +4. Add tests to cover missed lines. + +## Running Coverage +Run the following command to collect coverage in JSON format: +```bash +dart test --coverage=.dart_tool/coverage +``` + +> [!NOTE] +> We use `.dart_tool/coverage` as the output directory because `.dart_tool` +> is typically already ignored in `.gitignore` files. + +> [!TIP] +> For projects with complex conditional logic, you can pass the +> `--branch-coverage` flag to `dart test` to collect branch-level coverage. + +## Interpreting Results + +### Option 1: Use the custom interpreter script +This repository includes a zero-dependency script that parses the raw JSON +output and provides a summary of covered percentage and missed lines. + +Run it from the project root (adjust path to script as needed): +```bash +dart run .agent/skills/dart-test-coverage/scripts/interpret_coverage.dart .dart_tool/coverage +``` +Replace `` with the name from `pubspec.yaml`. + +Example Output: +``` +package:my_pkg/src/file.dart: 50.0% (2/4 lines) + Missed lines: 3, 4 +``` + +### Option 2: Use package:coverage +If `package:test` is installed, `package:coverage` is likely available as a +transitive dependency. You can use its `format_coverage` tool. + +To get a human-readable "pretty print" of the coverage: +```bash +dart run coverage:format_coverage --in=.dart_tool/coverage --out=stdout --pretty-print --report-on=lib +``` +This will output the file content with hit counts on the left (e.g., `0|` for +missed lines). + +## Best Practices for Reporting Results +When presenting coverage results to the user, follow these guidelines: +1. **State the high-level percentage first** to give immediate context. +2. **Identify specific files and missed lines** clearly. +3. **Translate line numbers to code**: Don't just say "lines 3-6 are missed". + Look at the source file and tell the user which functions or blocks are + untested (e.g., "The `divide` function is missing coverage"). +4. **Propose concrete fixes**: Provide example test code that the user can + immediately apply to cover the missed lines. +5. **Use tables for multi-file summaries**: When reporting on multiple files, + use a markdown table with columns for File, Coverage %, and Missed Lines + to make the summary easy to scan. + +## Constraints +- ALWAYS verify that tests pass before collecting coverage. +- DO NOT commit the `.dart_tool/coverage` directory. +- Focus coverage improvements on `lib/` files, not `test/` or generated files. diff --git a/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/lib/src/calculator.dart b/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/lib/src/calculator.dart new file mode 100644 index 00000000..e7704f63 --- /dev/null +++ b/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/lib/src/calculator.dart @@ -0,0 +1,7 @@ +int add(int a, int b) => a + b; +int subtract(int a, int b) => a - b; +int multiply(int a, int b) => a * b; +int divide(int a, int b) { + if (b == 0) throw ArgumentError('Cannot divide by zero'); + return a ~/ b; +} diff --git a/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/pubspec.lock b/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/pubspec.lock new file mode 100644 index 00000000..06f54a62 --- /dev/null +++ b/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/pubspec.lock @@ -0,0 +1,381 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: a49d6cf99e8d8e7a8e93668d09ced0bbdb954d0b4fccc2f5f9241c6b87fad95c + url: "https://pub.dev" + source: hosted + version: "99.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "663efa951fb8a45e06f491223a604c93820598f20e6a99c25617a1576065e8b7" + url: "https://pub.dev" + source: hosted + version: "12.1.0" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + url: "https://pub.dev" + source: hosted + version: "1.15.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + meta: + dependency: transitive + description: + name: meta + sha256: df0c643f44ad098eb37988027a8e2b2b5a031fd3977f06bbfd3a76637e8df739 + url: "https://pub.dev" + source: hosted + version: "1.18.2" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test: + dependency: "direct dev" + description: + name: test + sha256: "8d9ceddbab833f180fbefed08afa76d7c03513dfdba87ffcec2718b02bbcbf20" + url: "https://pub.dev" + source: hosted + version: "1.31.0" + test_api: + dependency: transitive + description: + name: test_api + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + url: "https://pub.dev" + source: hosted + version: "0.7.11" + test_core: + dependency: transitive + description: + name: test_core + sha256: "1991d4cfe85d5043241acac92962c3977c8d2f2add1ee73130c7b286417d1d34" + url: "https://pub.dev" + source: hosted + version: "0.6.17" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "046d3928e16fa4dc46e8350415661755ab759d9fc97fc21b5ab295f71e4f0499" + url: "https://pub.dev" + source: hosted + version: "15.1.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.9.0 <4.0.0" diff --git a/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/pubspec.yaml b/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/pubspec.yaml new file mode 100644 index 00000000..142515a9 --- /dev/null +++ b/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/pubspec.yaml @@ -0,0 +1,8 @@ +name: dummy_pkg +publish_to: none + +environment: + sdk: ^3.9.0 + +dev_dependencies: + test: ^1.24.0 diff --git a/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/test/calculator_test.dart b/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/test/calculator_test.dart new file mode 100644 index 00000000..df15effa --- /dev/null +++ b/tool/dart_skills_lint/.agents/skills/dart-test-coverage/example/test/calculator_test.dart @@ -0,0 +1,11 @@ +import 'package:dummy_pkg/src/calculator.dart'; +import 'package:test/test.dart'; + +void main() { + test('add', () { + expect(add(1, 2), 3); + }); + test('subtract', () { + expect(subtract(2, 1), 1); + }); +} diff --git a/tool/dart_skills_lint/.agents/skills/dart-test-coverage/scripts/interpret_coverage.dart b/tool/dart_skills_lint/.agents/skills/dart-test-coverage/scripts/interpret_coverage.dart new file mode 100644 index 00000000..87f60fcb --- /dev/null +++ b/tool/dart_skills_lint/.agents/skills/dart-test-coverage/scripts/interpret_coverage.dart @@ -0,0 +1,82 @@ +#!/usr/bin/env dart + +import 'dart:convert'; +import 'dart:io'; + +void main(List args) { + if (args.isEmpty) { + print( + 'Usage: dart interpret_coverage.dart [package_name]', + ); + exitCode = 64; // Bad usage + return; + } + final dirPath = args[0]; + final packageName = args.length > 1 ? args[1] : null; + + final dir = Directory(dirPath); + if (!dir.existsSync()) { + print('Directory not found: $dirPath'); + exitCode = 78; // Configuration error + return; + } + + final files = dir + .listSync(recursive: true) + .whereType() + .where((f) => f.path.endsWith('.vm.json')); + + final coverageData = >{}; // file -> (line -> hits) + + for (final file in files) { + final content = file.readAsStringSync(); + final json = jsonDecode(content) as Map; + final coverage = json['coverage'] as List; + + for (final item in coverage) { + final source = item['source'] as String; + + // Filter by package name if provided + if (packageName != null && !source.startsWith('package:$packageName/')) { + continue; + } + + // Skip test files usually + if (source.contains('/test/')) continue; + + final hits = item['hits'] as List; + final fileData = coverageData.putIfAbsent(source, () => {}); + + for (var i = 0; i < hits.length; i += 2) { + final line = hits[i] as int; + final count = hits[i + 1] as int; + fileData[line] = (fileData[line] ?? 0) + count; + } + } + } + + if (coverageData.isEmpty) { + print('No coverage data found for the specified criteria.'); + return; + } + + // Print summary + coverageData.forEach((source, lineHits) { + final totalLines = lineHits.length; + final coveredLines = lineHits.values.where((hits) => hits > 0).length; + final percent = totalLines > 0 + ? (coveredLines / totalLines * 100).toStringAsFixed(1) + : '100'; + + print('$source: $percent% ($coveredLines/$totalLines lines)'); + + // Show missed lines + final missed = + lineHits.entries.where((e) => e.value == 0).map((e) => e.key).toList() + ..sort(); + + if (missed.isNotEmpty) { + print(' Missed lines: ${missed.join(', ')}'); + } + }); +} diff --git a/tool/dart_skills_lint/.agents/skills/dart-test-fundamentals/SKILL.md b/tool/dart_skills_lint/.agents/skills/dart-test-fundamentals/SKILL.md index 51a93811..39390dd3 100644 --- a/tool/dart_skills_lint/.agents/skills/dart-test-fundamentals/SKILL.md +++ b/tool/dart_skills_lint/.agents/skills/dart-test-fundamentals/SKILL.md @@ -2,19 +2,30 @@ name: dart-test-fundamentals description: |- Core concepts and best practices for `package:test`. - Covers `test`, `group`, lifecycle methods (`setUp`, `tearDown`), and configuration (`dart_test.yaml`). + Covers `test`, `group`, lifecycle methods (`setUp`, `tearDown`), and + configuration (`dart_test.yaml`). license: Apache-2.0 --- # Dart Test Fundamentals ## When to use this skill + Use this skill when: - Writing new test files. -- structuring test suites with `group`. +- Structuring test suites with `group`. - Configuring test execution via `dart_test.yaml`. - Understanding test lifecycle methods. +## Discovery + +To find candidates for improving test structure: + +### `try-finally` Cleanup +Search for tests that use `try-finally` for cleanup instead of `addTearDown`: +- **Regex**: `\bfinally\s*\{` (Check if this is used for resource cleanup + inside a test). + ## Core Concepts ### 1. Test Structure (`test` and `group`) @@ -33,9 +44,12 @@ Use this skill when: (e.g., `group('MyClient', ...)`). - **Avoid Single Groups**: Do not wrap all tests in a file with a single `group` call if it's the only one. + - **NOTE**: DO NOT remove groups when doing a cleanup on existing code you + didn't create unless explicitly asked to. This can cause a LOT of churn + in the DIFF that most engineers won't want! -- **Naming Tests**: - - Avoid redundant "test" prefixes. +- **Naming Tests** `test('test name here',`: + - Avoid redundant "test" prefixes. Use `group` instead. - Include the expected behavior or outcome in the description (e.g., `'throws StateError'` or `'adds API key to URL'`). - Descriptions should read well when concatenated with their group name. @@ -62,7 +76,39 @@ Use this skill when: - Use `setUp` for resetting state to ensure test isolation. - Avoid sharing mutable state between tests without resetting it. -### 3. Configuration (`dart_test.yaml`) +### 3. Cleaning Up Resources + +- To clean up resources created WITHIN the `test` body, consider using + `addTearDown` instead of a `try-finally` block. + +**Avoid:** +```dart +test('can create and delete a file', () { + final file = File('temp.txt'); + try { + file.writeAsStringSync('hello'); + expect(file.readAsStringSync(), 'hello'); + } finally { + if (file.existsSync()) file.deleteSync(); + } +}); +``` + +**Prefer:** +```dart +test('can create and delete a file', () { + final file = File('temp.txt'); + // Register teardown immediately after resource creation intent + addTearDown(() { + if (file.existsSync()) file.deleteSync(); + }); + + file.writeAsStringSync('hello'); + expect(file.readAsStringSync(), 'hello'); +}); +``` + +### 4. Configuration (`dart_test.yaml`) The `dart_test.yaml` file configures the test runner. Common configurations include: @@ -102,7 +148,7 @@ timeouts: 2x # Double the default timeout ``` -### 4. File Naming +### 5. File Naming - Test files **must** end in `_test.dart` to be picked up by the test runner. - Place tests in the `test/` directory. diff --git a/tool/dart_skills_lint/.agents/skills/grill-me/SKILL.md b/tool/dart_skills_lint/.agents/skills/grill-me/SKILL.md new file mode 100644 index 00000000..bd04394c --- /dev/null +++ b/tool/dart_skills_lint/.agents/skills/grill-me/SKILL.md @@ -0,0 +1,10 @@ +--- +name: grill-me +description: Interview the user relentlessly about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions "grill me". +--- + +Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer. + +Ask the questions one at a time. + +If a question can be answered by exploring the codebase, explore the codebase instead. diff --git a/tool/dart_skills_lint/skills-lock.json b/tool/dart_skills_lint/skills-lock.json index 9dd66495..c6dd86f0 100644 --- a/tool/dart_skills_lint/skills-lock.json +++ b/tool/dart_skills_lint/skills-lock.json @@ -4,37 +4,57 @@ "dart-best-practices": { "source": "kevmoo/dash_skills", "sourceType": "github", - "computedHash": "1eb4a38aa94060d3d420f1b03b661ad784a1cd732ffb7ff4d8369d085024d4f5" + "computedHash": "1f529bd357368f67621de2c25531f69e16f726ecbe0071aabbf47b6d7b2a16c4" + }, + "dart-checks-migration": { + "source": "kevmoo/dash_skills", + "sourceType": "github", + "computedHash": "15592bf08008e2a37da5d1f4dc6b99dae01ce0db1ec7127f97cdfac3d6bcfb5d" }, "dart-cli-app-best-practices": { "source": "kevmoo/dash_skills", "sourceType": "github", - "computedHash": "6bd4fd96c0ac4dc59699332948ff9a8a724e9d3093cf13d28b542c7e70c92809" + "computedHash": "83487d2341e4b692d180fe6c9fc402706a5707aba227424db016182006cfdf30" }, "dart-doc-validation": { "source": "kevmoo/dash_skills", "sourceType": "github", - "computedHash": "5b2c3fd7a30ee04b35ef8914ea945efbec49087e4bd99fb211f421a12e43dc2e" + "computedHash": "acc150afe3406de17059f9215fa009ffeb1b7c0815f903c2af303380e90055c4" + }, + "dart-long-lines": { + "source": "kevmoo/dash_skills", + "sourceType": "github", + "computedHash": "db6543389e683823446c7ee27fb162f219cc47f209809b9fd79b012bdb944991" }, "dart-matcher-best-practices": { "source": "kevmoo/dash_skills", "sourceType": "github", - "computedHash": "11b0b422a571cab62fb7f945ba548947200cd22a5a8f7b5627af636d002c4ad9" + "computedHash": "99a200dca8676e865b97a81abe40e8f9e2b9afdbd0f82845c1e3c8dd7eae007b" }, "dart-modern-features": { "source": "kevmoo/dash_skills", "sourceType": "github", - "computedHash": "cb4cda90e1451aaa5a9a7d4d81358dc563ec07499ce1c18d282882d9c1bdae4e" + "computedHash": "3148ee2c96c296593d3cd704d4bd99af25c84237a40c07acad5696328555907a" }, "dart-package-maintenance": { "source": "kevmoo/dash_skills", "sourceType": "github", - "computedHash": "1bab37315f9ba1b6f6850a7563d58b5728c14b23a712bd391e3972c8dd84864a" + "computedHash": "92efdb1b873eec4e0bcb61ade541b954eee347c3ddd70c27ba22c982c318d7e6" + }, + "dart-test-coverage": { + "source": "kevmoo/dash_skills", + "sourceType": "github", + "computedHash": "38e3264a1d9f9f3687eb460ac66889432e0f2319857ba5a221df5dce31e6fe2d" }, "dart-test-fundamentals": { "source": "kevmoo/dash_skills", "sourceType": "github", - "computedHash": "fee07d6fdcfd2d5f73f055a165a24ff70e4de5fbf36678e95c4d9b299ec5fb5b" + "computedHash": "0d6e877c3575136909afa4f9daa82ed48c713a3052f8e211b2476043b0438bf7" + }, + "grill-me": { + "source": "mattpocock/skills", + "sourceType": "github", + "computedHash": "784f0dbb7403b0f00324bce9a112f715342777a0daee7bbb7385f9c6f0a170ea" }, "test-driven-development": { "source": "obra/superpowers",