From 41fa00d48daa1b2ce219c421a43c464c9481f6a7 Mon Sep 17 00:00:00 2001 From: Leo Farias Date: Wed, 8 Apr 2026 22:28:25 -0400 Subject: [PATCH] chore: add Dart agent skills from flutter/skills Install 9 Dart-focused skills (dart-best-practices, dart-test-fundamentals, dart-modern-features, dart-package-maintenance, dart-matcher-best-practices, dart-cli-app-best-practices, dart-doc-validation, test-driven-development, add-dart-lint-validation-rule) from the flutter/skills repository. Skills are stored in .agents/skills/ with symlinks in .claude/skills/. Update .gitignore to track .claude/skills/ while keeping other .claude files ignored. --- .../add-dart-lint-validation-rule/SKILL.md | 146 +++++++ .agents/skills/dart-best-practices/SKILL.md | 52 +++ .../dart-cli-app-best-practices/SKILL.md | 123 ++++++ .agents/skills/dart-doc-validation/SKILL.md | 45 +++ .../dart-matcher-best-practices/SKILL.md | 106 +++++ .agents/skills/dart-modern-features/SKILL.md | 241 ++++++++++++ .../skills/dart-package-maintenance/SKILL.md | 75 ++++ .../skills/dart-test-fundamentals/SKILL.md | 124 ++++++ .../skills/test-driven-development/SKILL.md | 371 ++++++++++++++++++ .../testing-anti-patterns.md | 299 ++++++++++++++ .claude/skills/add-dart-lint-validation-rule | 1 + .claude/skills/dart-best-practices | 1 + .claude/skills/dart-cli-app-best-practices | 1 + .claude/skills/dart-doc-validation | 1 + .claude/skills/dart-matcher-best-practices | 1 + .claude/skills/dart-modern-features | 1 + .claude/skills/dart-package-maintenance | 1 + .claude/skills/dart-test-fundamentals | 1 + .claude/skills/test-driven-development | 1 + .gitignore | 3 +- 20 files changed, 1593 insertions(+), 1 deletion(-) create mode 100644 .agents/skills/add-dart-lint-validation-rule/SKILL.md create mode 100644 .agents/skills/dart-best-practices/SKILL.md create mode 100644 .agents/skills/dart-cli-app-best-practices/SKILL.md create mode 100644 .agents/skills/dart-doc-validation/SKILL.md create mode 100644 .agents/skills/dart-matcher-best-practices/SKILL.md create mode 100644 .agents/skills/dart-modern-features/SKILL.md create mode 100644 .agents/skills/dart-package-maintenance/SKILL.md create mode 100644 .agents/skills/dart-test-fundamentals/SKILL.md create mode 100644 .agents/skills/test-driven-development/SKILL.md create mode 100644 .agents/skills/test-driven-development/testing-anti-patterns.md create mode 120000 .claude/skills/add-dart-lint-validation-rule create mode 120000 .claude/skills/dart-best-practices create mode 120000 .claude/skills/dart-cli-app-best-practices create mode 120000 .claude/skills/dart-doc-validation create mode 120000 .claude/skills/dart-matcher-best-practices create mode 120000 .claude/skills/dart-modern-features create mode 120000 .claude/skills/dart-package-maintenance create mode 120000 .claude/skills/dart-test-fundamentals create mode 120000 .claude/skills/test-driven-development diff --git a/.agents/skills/add-dart-lint-validation-rule/SKILL.md b/.agents/skills/add-dart-lint-validation-rule/SKILL.md new file mode 100644 index 0000000..a8a3995 --- /dev/null +++ b/.agents/skills/add-dart-lint-validation-rule/SKILL.md @@ -0,0 +1,146 @@ +--- +name: add-dart-lint-validation-rule +description: Instructions for adding a new validation rule and CLI flag to dart_skills_lint. +--- + +# Add a New Validation Rule and Flag + +Use this skill when you need to add a new validation rule to the `dart_skills_lint` package, expose it as a toggleable CLI flag, and verify its behavior. + +--- + +## ๐Ÿ› ๏ธ Step-by-Step Implementation + +### 1. Define the Rule in `lib/src/rules.dart` + +To create a new rule, add a top-level `CheckType` instance. + +```dart +// lib/src/rules.dart + +/// Template instance for checking if the description has a trailing period. +final descriptionTrailingPeriodCheck = CheckType( + name: 'description-trailing-period', + defaultSeverity: AnalysisSeverity.error, // or warning/disabled +); +``` + +### 2. Expose the CLI Flag in `lib/src/entry_point.dart` + +Modify `runApp` to include the toggleable flag for your new rule. + +#### ๐Ÿ“‹ Register Parser Option +Add `.addFlag` in `runApp` (around line 60-90): + +```dart +// lib/src/entry_point.dart + +parser + ..addFlag(descriptionTrailingPeriodCheck.name, + negatable: true, + defaultsTo: true, + help: 'Check if the description ends with a period.'); +``` + +#### ๐ŸŽš๏ธ Resolve Flag Logic or Override Severities +Find where severities are evaluated (around line 140-170) and bind your flag state: + +```dart +if (results.wasParsed(descriptionTrailingPeriodCheck.name)) { + descriptionTrailingPeriodCheck.severity = (results[descriptionTrailingPeriodCheck.name] as bool) + ? descriptionTrailingPeriodCheck.defaultSeverity + : AnalysisSeverity.disabled; +} +``` + +Add your rule to the `checkTypes` set if you want it loaded by default configuration overrides. + +### 3. Implement Validation in `lib/src/validator.dart` + +Write the specific logic inside `Validator` checking the schema. + +```dart +// lib/src/validator.dart + +void _validateDescriptionPeriod(String description, List validationErrors) { + if (description.isNotEmpty && !description.endsWith('.')) { + validationErrors.add(ValidationError( + ruleId: _getRule(descriptionTrailingPeriodCheck).name, + file: _skillFileName, + message: 'Description must end with a period.', + severity: _getRule(descriptionTrailingPeriodCheck).severity, + )); + } +} +``` + +Invoke your sub-routine inside `_parseMetadataFields`: + +```dart +final description = yaml[_descriptionField]?.toString() ?? ''; +if (description.isNotEmpty) { + _validateDescriptionPeriod(description, validationErrors); +} +``` + +--- + +## ๐Ÿงช Testing the New Rule + +You must write automated tests verifying your rule triggers when it should and skips when it shouldn't. + +### Creating a Test Suite Case +Add matching suite files in `test/` (e.g., `test/field_constraints_test.dart` or `test/metadata_validation_test.dart`). + +```dart +// test/field_constraints_test.dart + +test('triggers error when description does not end with period', () async { + final tempDir = createTempSkillDir( + name: 'test-skill', + description: 'This description does not end with a period', + ); + + final validator = Validator(rules: {descriptionTrailingPeriodCheck}); + final result = await validator.validate(tempDir); + + expect(result.validationErrors.any((e) => e.ruleId == 'description-trailing-period'), isTrue); +}); +test('skips error when description ends with period', () async { + final tempDir = createTempSkillDir( + name: 'test-skill', + description: 'This description ends with a period.', + ); + + final validator = Validator(rules: {descriptionTrailingPeriodCheck}); + final result = await validator.validate(tempDir); + + expect(result.validationErrors.any((e) => e.ruleId == 'description-trailing-period'), isFalse); +}); +``` + +--- + +## ๐Ÿ“š Documentation Updates + +When a new rule is introduced, verify that you synchronize sibling markdown files! + +1. **`README.md`:** + * Add your flag under the **Usage** and **Flags** sections so users know it exists. + * Add descriptive lines under **Specification Validation**. +2. **`documentation/knowledge/SPECIFICATION.md`:** + * If the rule implements standard specifications traits, add constraints parameters under Section 5.1 (Validation parameters). + +--- + +## ๐Ÿšฆ Checklist Before Submitting PR + +- [ ] Rule defined in `rules.dart`. +- [ ] Flag registered in `entry_point.dart`. +- [ ] Logic implemented in `validator.dart`. +- [ ] Toggle logic tests added in `test/`. +- [ ] Usage listed in `README.md`. +- [ ] Schema documented in `documentation/knowledge/SPECIFICATION.md`. +- [ ] Run `dart analyze` to ensure no issues. +- [ ] Run `dart test` to ensure tests passing. +- [ ] Run `dart format .` to format code. diff --git a/.agents/skills/dart-best-practices/SKILL.md b/.agents/skills/dart-best-practices/SKILL.md new file mode 100644 index 0000000..2bed63e --- /dev/null +++ b/.agents/skills/dart-best-practices/SKILL.md @@ -0,0 +1,52 @@ +--- +name: dart-best-practices +description: |- + General best practices for Dart development. + Covers code style, effective Dart, and language features. +license: Apache-2.0 +--- + +# Dart Best Practices + +## 1. When to use this skill +Use this skill when: +- Writing or reviewing Dart code. +- Looking for guidance on idiomatic Dart usage. + +## 2. Best Practices + +### Multi-line Strings +Prefer using multi-line strings (`'''`) over concatenating strings with `+` and +`\n`, especially for large blocks of text like SQL queries, HTML, or +PEM-encoded keys. This improves readability and avoids +`lines_longer_than_80_chars` lint errors by allowing natural line breaks. + +**Avoid:** +```dart +final pem = '-----BEGIN RSA PRIVATE KEY-----\n' + + base64Encode(fullBytes) + + '\n-----END RSA PRIVATE KEY-----'; +``` + +**Prefer:** +```dart +final pem = ''' +-----BEGIN RSA PRIVATE KEY----- +${base64Encode(fullBytes)} +-----END RSA PRIVATE KEY-----'''; +``` + +### Line Length +Avoid lines longer than 80 characters, even in Markdown files and comments. +This ensures code is readable in split-screen views and on smaller screens +without horizontal scrolling. + +**Prefer:** +Target 80 characters for wrapping text. Exceptions are allowed for long URLs +or identifiers that cannot be broken. + +## Related Skills + +- **[`dart-modern-features`](../dart-modern-features/SKILL.md)**: For idiomatic + usage of modern Dart features like Pattern Matching (useful for deep JSON + extraction), Records, and Switch Expressions. diff --git a/.agents/skills/dart-cli-app-best-practices/SKILL.md b/.agents/skills/dart-cli-app-best-practices/SKILL.md new file mode 100644 index 0000000..6dad82a --- /dev/null +++ b/.agents/skills/dart-cli-app-best-practices/SKILL.md @@ -0,0 +1,123 @@ +--- +name: dart-cli-app-best-practices +description: |- + Best practices for creating high-quality, executable Dart CLI applications. + Covers entrypoint structure, exit code handling, and recommended packages. +license: Apache-2.0 +--- + +# Dart CLI Application Best Practices + +## 1. When to use this skill +Use this skill when: +- Creating a new Dart CLI application. +- Refactoring an existing CLI entrypoint (`bin/`). +- Reviewing CLI code for quality and standards. +- Setting up executable scripts for Linux/Mac. + +## 2. Best Practices + +### Entrypoint Structure (`bin/`) +Keep the contents of your entrypoint file (e.g., `bin/my_app.dart`) minimal. +This improves testability by decoupling logic from the process runner. + +**DO:** +```dart +// bin/my_app.dart +import 'package:my_app/src/entry_point.dart'; + +Future main(List arguments) async { + await runApp(arguments); +} +``` + +**DON'T:** +- Put complex logic directly in `bin/my_app.dart`. +- Define classes or heavy functions in the entrypoint. + +### Executable Scripts +For CLI tools intended to be run directly on Linux and Mac, add a shebang and +ensure the file is executable. + +**DO:** +1. Add `#!/usr/bin/env dart` to the first line. +2. Run `chmod +x bin/my_script.dart` to make it executable. + +```dart +#!/usr/bin/env dart + +void main() => print('Ready to run!'); +``` + +### Process Termination (`exitCode`) +Properly handle process termination to allow for debugging and correct status +reporting. + +**DO:** +- Use the `exitCode` setter to report failure. +- Allow `main` to complete naturally. +- Use standard exit codes (sysexits) for clarity (e.g., `64` for bad usage, + `78` for configuration errors). + - See `package:io` `ExitCode` class or FreeBSD sysexits man page. + +```dart +import 'dart:io'; + +void main() { + if (someFailure) { + exitCode = 64; // DO! + return; + } +} +``` + +**AVOID:** +- Calling `exit(code)` directly, as it terminates the VM immediately, + preventing "pause on exit" debugging and `finally` blocks from running. + +### Exception Handling +Uncaught exceptions automatically set a non-zero exit code, but you should +handle expected errors gracefully. + +**Example:** +```dart +Future main(List arguments) async { + try { + await runApp(arguments); + } catch (e, stack) { + print('App crashed!'); + print(e); + print(stack); + exitCode = 1; // Explicitly fail + } +} +``` + +## 3. Recommended Packages + +Use these community-standard packages owned by the [Dart team](https://dart.dev) +to solve common CLI problems: + +| Category | Recommended Package | Usage | +| :--- | :--- | :--- | +| **Stack Traces** | [`package:stack_trace`](https://pub.dev/packages/stack_trace) | detailed, cleaner stack traces | +| **Arg Parsing** | [`package:args`](https://pub.dev/packages/args) | standard flag/option parsing | +| **Testing** | [`package:test_process`](https://pub.dev/packages/test_process) | integration testing for CLI apps | +| **Testing** | [`package:test_descriptor`](https://pub.dev/packages/test_descriptor) | file system fixtures for tests | +| **Networking** | [`package:http`](https://pub.dev/packages/http) | standard HTTP client (remember user-agent!) | +| **ANSI Output** | [`package:io`](https://pub.dev/packages/io) | handling ANSI colors and styles | + +## 4. Interesting community packages + +| Category | Recommended Package | Usage | +| :--- | :--- | :--- | +| **Configuration** | [`package:json_serializable`](https://pub.dev/packages/json_serializable) | strongly typed config objects | +| **CLI Generation** | [`package:build_cli`](https://pub.dev/packages/build_cli) | generate arg parsers from classes | +| **Version Info** | [`package:build_version`](https://pub.dev/packages/build_version) | automatic version injection | +| **Configuration** | [`package:checked_yaml`](https://pub.dev/packages/checked_yaml) | precise YAML parsing with line numbers | + +## 5. Conventions + +- **File Caching**: Write cached files to `.dart_tool/[pkg_name]/`. +- **User-Agent**: Always set a User-Agent header in HTTP requests, including + version info. diff --git a/.agents/skills/dart-doc-validation/SKILL.md b/.agents/skills/dart-doc-validation/SKILL.md new file mode 100644 index 0000000..4800659 --- /dev/null +++ b/.agents/skills/dart-doc-validation/SKILL.md @@ -0,0 +1,45 @@ +--- +name: dart-doc-validation +description: |- + Best practices for validating Dart documentation comments. + Covers using `dart doc` to catch unresolved references and macros. +license: Apache-2.0 +--- + +# Dart Doc Validation + +## 1. When to use this skill + +Use this skill when: +- Writing or updating documentation comments (`///`) in Dart code. +- Checking for broken documentation links, references, or macros. +- Preparing a package for publishing to pub.dev. + +## 2. Best Practices + +### Validating Documentation Locally + +Use the `dart doc` command with a temporary output directory to validate +documentation comments without polluting the local project workspace. + +This command parses all documentation comments and reports warnings such as: +- `warning: unresolved doc reference` +- `warning: undefined macro` + +**Command to run:** +```bash +dart doc -o $(mktemp -d) +``` + +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. + +### Fixing Common Warnings + +- **Unresolved doc reference**: Ensure that any identifier wrapped in square + brackets (`[Identifier]`) correctly points to an existing class, method, + property, or parameter in the current scope or imported libraries. +- **Undefined macro**: If using `{@macro macro_name}`, ensure that the + template `{@template macro_name}` is defined in the same file or a file + that is imported and visible to the documentation generator. diff --git a/.agents/skills/dart-matcher-best-practices/SKILL.md b/.agents/skills/dart-matcher-best-practices/SKILL.md new file mode 100644 index 0000000..b0a9147 --- /dev/null +++ b/.agents/skills/dart-matcher-best-practices/SKILL.md @@ -0,0 +1,106 @@ +--- +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. +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. + +## Core Matchers + +### 1. Collections (`hasLength`, `contains`, `isEmpty`) + +- **`hasLength(n)`**: + - Prefer `expect(list, hasLength(n))` over `expect(list.length, n)`. + - Gives better error messages on failure (shows actual list content). + +- **`isEmpty` / `isNotEmpty`**: + - Prefer `expect(list, isEmpty)` over `expect(list.isEmpty, true)`. + - Prefer `expect(list, isNotEmpty)` over `expect(list.isNotEmpty, true)`. + +- **`contains(item)`**: + - Verify existence without manual iteration. + +- **`unorderedEquals(items)`**: + - Verify contents regardless of order. + +### 2. Type Checks (`isA` and `TypeMatcher`) + +- **`isA()`**: + - Prefer for inline assertions: `expect(obj, isA())`. + - More concise and readable than `TypeMatcher()`. + - Allows chaining constraints using `.having()`. + +- **`TypeMatcher`**: + - Prefer when defining top-level reusable matchers. + - **Use `const`**: `const isMyType = TypeMatcher();` + - Chaining `.having()` works here too, but the resulting matcher is not `const`. + +### 3. Object Properties (`having`) + +Use `.having()` on `isA()` or other TypeMatchers to check properties. + +- **Descriptive Names**: Use meaningful parameter names in the closure (e.g., + `(e) => e.message`) instead of generic ones like `p0` to improve readability. + +```dart +expect(person, isA() + .having((p) => p.name, 'name', 'Alice') + .having((p) => p.age, 'age', greaterThan(18))); +``` + +This provides detailed failure messages indicating exactly which property +failed. + +### 4. Async Assertions + +- **`completion(matcher)`**: + - Wait for a future to complete and check its value. + - **Prefer `await expectLater(...)`** to ensure the future completes before + the test continues. + - `await expectLater(future, completion(equals(42)))`. + +- **`throwsA(matcher)`**: + - Check that a future or function throws an exception. + - `await expectLater(future, throwsA(isA()))`. + - `expect(() => function(), throwsA(isA()))` (synchronous + function throwing is fine with `expect`). + +### 5. Using `expectLater` + +Use `await expectLater(...)` when testing async behavior to ensure proper +sequencing. + +```dart +// GOOD: Waits for future to complete before checking side effects +await expectLater(future, completion(equals(42))); +expect(sideEffectState, equals('done')); + +// BAD: Side effect check might run before future completes +expect(future, completion(equals(42))); +expect(sideEffectState, equals('done')); // Race condition! +``` + +## Principles + +1. **Readable Failures**: Choose matchers that produce clear error messages. +2. **Avoid Manual Logic**: Don't use `if` statements or `for` loops for + assertions; let matchers handle it. +3. **Specific Matchers**: Use the most specific matcher available (e.g., + `containsPair` for maps instead of checking keys manually). + +## Related Skills + +- **[`dart-test-fundamentals`](../dart-test-fundamentals/SKILL.md)**: Core + concepts for structuring tests, lifecycles, and configuration. +- **[`dart-checks-migration`](../dart-checks-migration/SKILL.md)**: Use this + skill if you are migrating tests from `package:matcher` to modern + `package:checks`. diff --git a/.agents/skills/dart-modern-features/SKILL.md b/.agents/skills/dart-modern-features/SKILL.md new file mode 100644 index 0000000..9328219 --- /dev/null +++ b/.agents/skills/dart-modern-features/SKILL.md @@ -0,0 +1,241 @@ +--- +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, + 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. + +## 2. Features + +### Records +Use records as anonymous, immutable, aggregate structures to bundle multiple +objects without defining a custom class. Prefer them for returning multiple +values from a function or grouping related data temporarily. + +**Avoid:** +Creating a dedicated class for simple multiple-value returns. +```dart +class UserResult { + final String name; + final int age; + UserResult(this.name, this.age); +} + +UserResult fetchUser() { + return UserResult('Alice', 42); +} +``` + +**Prefer:** +Using records to bundle types seamlessly on the fly. +```dart +(String, int) fetchUser() { + return ('Alice', 42); +} + +void main() { + var user = fetchUser(); + print(user.$1); // Alice +} +``` + +### Patterns and Pattern Matching +Use patterns to destructure complex data into local variables and match against +specific shapes or values. Use them in `switch`, `if-case`, or variable +declarations to unpack data directly. + +**Avoid:** +Manually checking types, nulls, and keys for data extraction. +```dart +void processJson(Map json) { + if (json.containsKey('name') && json['name'] is String && + json.containsKey('age') && json['age'] is int) { + String name = json['name']; + int age = json['age']; + print('$name is $age years old.'); + } +} +``` + +**Prefer:** +Combining type-checking, validation, and assignment into a single statement. +```dart +void processJson(Map json) { + if (json case {'name': String name, 'age': int age}) { + print('$name is $age years old.'); + } +} +``` + +### Switch Expressions +Use switch expressions to return a value directly, eliminating bulky `case` and +`break` statements. + +**Avoid:** +Using switch statements where every branch simply returns or assigns a value. +```dart +String describeStatus(int code) { + switch (code) { + case 200: + return 'Success'; + case 404: + return 'Not Found'; + default: + return 'Unknown'; + } +} +``` + +**Prefer:** +Returning the evaluated expression directly using the `=>` syntax. +```dart +String describeStatus(int code) => switch (code) { + 200 => 'Success', + 404 => 'Not Found', + _ => 'Unknown', +}; +``` + +### Class Modifiers +Use class modifiers (`sealed`, `final`, `base`, `interface`) to restrict how +classes can be used outside their defines library. Prefer `sealed` for defining +closed families of subtypes to enable exhaustive checking. + +**Avoid:** +Using open `abstract` classes when the set of subclasses is known and fixed. +```dart +abstract class Result {} + +class Success extends Result {} +class Failure extends Result {} + +String handle(Result r) { + if (r is Success) return 'OK'; + if (r is Failure) return 'Error'; + return 'Unknown'; +} +``` + +**Prefer:** +Using `sealed` to guarantee to the compiler that all cases are covered. +```dart +sealed class Result {} + +class Success extends Result {} +class Failure extends Result {} + +String handle(Result r) => switch(r) { + Success() => 'OK', + Failure() => 'Error', +}; +``` + +### Extension Types +Use extension types for a zero-cost wrapper around an existing type. Use them to +restrict operations or add custom behavior without runtime overhead. + +**Avoid:** +Allocating new wrapper objects just for domain-specific logic or type safety. +```dart +class Id { + final int value; + Id(this.value); + bool get isValid => value > 0; +} +``` + +**Prefer:** +Using extension types which compile down to the underlying type at runtime. +```dart +extension type Id(int value) { + bool get isValid => value > 0; +} +``` + +### Digit Separators +Use underscores (`_`) in number literals strictly to improve visual readability +of large numeric values. + +**Avoid:** +Long number literals that are difficult to read at a glance. +```dart +const int oneMillion = 1000000; +``` + +**Prefer:** +Using underscores to separate thousands or other groupings. +```dart +const int oneMillion = 1_000_000; +``` + +### Wildcard Variables +Use wildcards (`_`) as non-binding variables or parameters to explicitly signal +that a value is intentionally unused. + +**Avoid:** +Inventing clunky, distinct variable names to avoid "unused variable" warnings. +```dart +void handleEvent(String ignoredName, int status) { + print('Status: $status'); +} +``` + +**Prefer:** +Explicitly dropping the binding with an underscore. +```dart +void handleEvent(String _, int status) { + print('Status: $status'); +} +``` + +### Null-Aware Elements +Use null-aware elements (`?`) inside collection literals to conditionally +include items only if they evaluate to a non-null value. + +**Avoid:** +Using collection `if` statements for simple null checks. +```dart +var names = [ + 'Alice', + if (optionalName != null) optionalName, + 'Charlie' +]; +``` + +**Prefer:** +Using the `?` prefix inline. +```dart +var names = ['Alice', ?optionalName, 'Charlie']; +``` + +### Dot Shorthands +Use dot shorthands to omit the explicit type name when it can be confidently +inferred from context, such as with enums or static fields. + +**Avoid:** +Fully qualifying type names when the type is obvious from the context. +```dart +LogLevel currentLevel = LogLevel.info; +``` + +**Prefer:** +Reducing visual noise with inferred shorthand. +```dart +LogLevel currentLevel = .info; +``` + +## Related Skills + +- **[`dart-best-practices`](../dart-best-practices/SKILL.md)**: General code + style and foundational Dart idioms that predate or complement the modern + syntax features. diff --git a/.agents/skills/dart-package-maintenance/SKILL.md b/.agents/skills/dart-package-maintenance/SKILL.md new file mode 100644 index 0000000..a0f7942 --- /dev/null +++ b/.agents/skills/dart-package-maintenance/SKILL.md @@ -0,0 +1,75 @@ +--- +name: dart-package-maintenance +description: |- + Guidelines for maintaining external Dart packages, covering versioning, + publishing workflows, and pull request management. Use when updating Dart + packages, preparing for a release, or managing collaborative changes in a + repository. +--- + +# Dart Package Maintenance + +Guidelines for maintaining Dart packages in alignment with Dart team best +practices. + +## Versioning + +### Semantic Versioning +- **Major**: Breaking changes. +- **Minor**: New features (non-breaking API changes). +- **Patch**: Bug fixes, documentation, or non-impacting changes. +- **Unstable packages**: Use `0.major.minor+patch`. +- **Recommendation**: Aim for `1.0.0` as soon as the package is stable. + +### Pre-Edit Verification +- **Check Published Versions**: Before modifying `CHANGELOG.md` or + `pubspec.yaml`, ALWAYS check the currently released version (e.g., via + `git tag` or `pub.dev`). +- **Do Not Amend Released Versions**: Never add new entries to a version header + that corresponds to a released tag. +- **Increment for New Changes**: If the current version in `pubspec.yaml` + matches a released tag, increment the version (e.g., usually to `-wip`) and + create a new section in `CHANGELOG.md`. + + - **Consistency**: The `CHANGELOG.md` header must match the new + `pubspec.yaml` version. + + - **SemVer Guidelines**: + - **Breaking Changes**: Bump Major, reset Minor/Patch + (e.g., `2.0.0-wip`, `0.5.0-wip`). + - **New Features**: Bump Minor, reset Patch + (e.g., `1.1.0-wip`, `0.4.5-wip`). + - **Bug Fixes**: Bump Patch (e.g., `1.0.1-wip`). + +### 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`). +- This indicates the current state is not yet published. + +### Breaking Changes +- Evaluate the impact on dependent packages and internal projects. +- Consider running changes through internal presubmits if possible. +- Prefer incremental rollouts (e.g., new behavior as opt-in) to minimize + downstream breakage. + +## Publishing Process + +1. **Preparation**: Remove the `-wip` suffix from `pubspec.yaml` and + `CHANGELOG.md` in a dedicated pull request. +2. **Execution**: Run `dart pub publish` (or `flutter pub publish`) and resolve + all warnings and errors. +3. **Tagging**: Create and push a git tag for the published version: + - For single-package repos: `v1.2.3` + - For monorepos: `package_name-v1.2.3` + - Example: `git tag v1.2.3 && git push --tags` + +## Pull Request Management + +- **Commits**: Each PR should generally correspond to a single squashed commit + upon merging. +- **Shared History**: Once a PR is open, avoid force pushing to the branch. +- **Conflict Resolution**: Prefer merging `main` into the PR branch rather than + rebasing to resolve conflicts. This preserves the review history and comments. +- **Reviewing**: Add comments from the "Files changed" view to batch them. +- **Local Inspection**: Use `gh pr checkout ` to inspect changes + locally in your IDE. diff --git a/.agents/skills/dart-test-fundamentals/SKILL.md b/.agents/skills/dart-test-fundamentals/SKILL.md new file mode 100644 index 0000000..e4a99b7 --- /dev/null +++ b/.agents/skills/dart-test-fundamentals/SKILL.md @@ -0,0 +1,124 @@ +--- +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`). +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`. +- Configuring test execution via `dart_test.yaml`. +- Understanding test lifecycle methods. + +## Core Concepts + +### 1. Test Structure (`test` and `group`) + +- **`test`**: The fundamental unit of testing. + ```dart + test('description', () { + // assertions + }); + ``` +- **`group`**: Used to organize tests into logical blocks. + - Groups can be nested. + - Descriptions are concatenated (e.g., "Group Description Test Description"). + - Helps scope `setUp` and `tearDown` calls. + - **Naming**: Use `PascalCase` for groups that correspond to a class name + (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. + +- **Naming Tests**: + - Avoid redundant "test" prefixes. + - 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. + +- **Named Parameters Placement**: + - For `test` and `group` calls, place named parameters (e.g., `testOn`, + `timeout`, `skip`) immediately after the description string, before the + callback closure. This improves readability by keeping the test logic last. + ```dart + test('description', testOn: 'vm', () { + // assertions + }); + ``` + +### 2. Lifecycle Methods (`setUp`, `tearDown`) + +- **`setUp`**: Runs *before* every `test` in the current `group` (and nested + groups). +- **`tearDown`**: Runs *after* every `test` in the current `group`. +- **`setUpAll`**: Runs *once* before any test in the group. +- **`tearDownAll`**: Runs *once* after all tests in the group. + +**Best Practice:** +- Use `setUp` for resetting state to ensure test isolation. +- Avoid sharing mutable state between tests without resetting it. + +### 3. Configuration (`dart_test.yaml`) + +The `dart_test.yaml` file configures the test runner. Common configurations +include: + +#### Platforms +Define where tests run (vm, chrome, node). + +```yaml +platforms: + - vm + - chrome +``` + +#### Tags +Categorize tests to run specific subsets. + +```yaml +tags: + integration: + timeout: 2x +``` + +Usage in code: +```dart +@Tags(['integration']) +import 'package:test/test.dart'; +``` + +Running tags: +`dart test --tags integration` + +#### Timeouts +Set default timeouts for tests. + +```yaml +timeouts: + 2x # Double the default timeout +``` + +### 4. File Naming +- Test files **must** end in `_test.dart` to be picked up by the test runner. +- Place tests in the `test/` directory. + +## Common commands + +- `dart test`: Run all tests. +- `dart test test/path/to/file_test.dart`: Run a specific file. +- `dart test --name "substring"`: Run tests matching a description. + +## Related Skills + +`dart-test-fundamentals` is the core skill for structuring and configuring +tests. For writing assertions within those tests, refer to: + +- **[`dart-matcher-best-practices`](../dart-matcher-best-practices/SKILL.md)**: + Use this if the project sticks with the traditional + `package:matcher` (`expect` calls). +- **[`dart-checks-migration`](../dart-checks-migration/SKILL.md)**: Use this + if the project is migrating to the modern `package:checks` (`check` calls). diff --git a/.agents/skills/test-driven-development/SKILL.md b/.agents/skills/test-driven-development/SKILL.md new file mode 100644 index 0000000..7a751fa --- /dev/null +++ b/.agents/skills/test-driven-development/SKILL.md @@ -0,0 +1,371 @@ +--- +name: test-driven-development +description: Use when implementing any feature or bugfix, before writing implementation code +--- + +# Test-Driven Development (TDD) + +## Overview + +Write the test first. Watch it fail. Write minimal code to pass. + +**Core principle:** If you didn't watch the test fail, you don't know if it tests the right thing. + +**Violating the letter of the rules is violating the spirit of the rules.** + +## When to Use + +**Always:** +- New features +- Bug fixes +- Refactoring +- Behavior changes + +**Exceptions (ask your human partner):** +- Throwaway prototypes +- Generated code +- Configuration files + +Thinking "skip TDD just this once"? Stop. That's rationalization. + +## The Iron Law + +``` +NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST +``` + +Write code before the test? Delete it. Start over. + +**No exceptions:** +- Don't keep it as "reference" +- Don't "adapt" it while writing tests +- Don't look at it +- Delete means delete + +Implement fresh from tests. Period. + +## Red-Green-Refactor + +```dot +digraph tdd_cycle { + rankdir=LR; + red [label="RED\nWrite failing test", shape=box, style=filled, fillcolor="#ffcccc"]; + verify_red [label="Verify fails\ncorrectly", shape=diamond]; + green [label="GREEN\nMinimal code", shape=box, style=filled, fillcolor="#ccffcc"]; + verify_green [label="Verify passes\nAll green", shape=diamond]; + refactor [label="REFACTOR\nClean up", shape=box, style=filled, fillcolor="#ccccff"]; + next [label="Next", shape=ellipse]; + + red -> verify_red; + verify_red -> green [label="yes"]; + verify_red -> red [label="wrong\nfailure"]; + green -> verify_green; + verify_green -> refactor [label="yes"]; + verify_green -> green [label="no"]; + refactor -> verify_green [label="stay\ngreen"]; + verify_green -> next; + next -> red; +} +``` + +### RED - Write Failing Test + +Write one minimal test showing what should happen. + + +```typescript +test('retries failed operations 3 times', async () => { + let attempts = 0; + const operation = () => { + attempts++; + if (attempts < 3) throw new Error('fail'); + return 'success'; + }; + + const result = await retryOperation(operation); + + expect(result).toBe('success'); + expect(attempts).toBe(3); +}); +``` +Clear name, tests real behavior, one thing + + + +```typescript +test('retry works', async () => { + const mock = jest.fn() + .mockRejectedValueOnce(new Error()) + .mockRejectedValueOnce(new Error()) + .mockResolvedValueOnce('success'); + await retryOperation(mock); + expect(mock).toHaveBeenCalledTimes(3); +}); +``` +Vague name, tests mock not code + + +**Requirements:** +- One behavior +- Clear name +- Real code (no mocks unless unavoidable) + +### Verify RED - Watch It Fail + +**MANDATORY. Never skip.** + +```bash +npm test path/to/test.test.ts +``` + +Confirm: +- Test fails (not errors) +- Failure message is expected +- Fails because feature missing (not typos) + +**Test passes?** You're testing existing behavior. Fix test. + +**Test errors?** Fix error, re-run until it fails correctly. + +### GREEN - Minimal Code + +Write simplest code to pass the test. + + +```typescript +async function retryOperation(fn: () => Promise): Promise { + for (let i = 0; i < 3; i++) { + try { + return await fn(); + } catch (e) { + if (i === 2) throw e; + } + } + throw new Error('unreachable'); +} +``` +Just enough to pass + + + +```typescript +async function retryOperation( + fn: () => Promise, + options?: { + maxRetries?: number; + backoff?: 'linear' | 'exponential'; + onRetry?: (attempt: number) => void; + } +): Promise { + // YAGNI +} +``` +Over-engineered + + +Don't add features, refactor other code, or "improve" beyond the test. + +### Verify GREEN - Watch It Pass + +**MANDATORY.** + +```bash +npm test path/to/test.test.ts +``` + +Confirm: +- Test passes +- Other tests still pass +- Output pristine (no errors, warnings) + +**Test fails?** Fix code, not test. + +**Other tests fail?** Fix now. + +### REFACTOR - Clean Up + +After green only: +- Remove duplication +- Improve names +- Extract helpers + +Keep tests green. Don't add behavior. + +### Repeat + +Next failing test for next feature. + +## Good Tests + +| Quality | Good | Bad | +|---------|------|-----| +| **Minimal** | One thing. "and" in name? Split it. | `test('validates email and domain and whitespace')` | +| **Clear** | Name describes behavior | `test('test1')` | +| **Shows intent** | Demonstrates desired API | Obscures what code should do | + +## Why Order Matters + +**"I'll write tests after to verify it works"** + +Tests written after code pass immediately. Passing immediately proves nothing: +- Might test wrong thing +- Might test implementation, not behavior +- Might miss edge cases you forgot +- You never saw it catch the bug + +Test-first forces you to see the test fail, proving it actually tests something. + +**"I already manually tested all the edge cases"** + +Manual testing is ad-hoc. You think you tested everything but: +- No record of what you tested +- Can't re-run when code changes +- Easy to forget cases under pressure +- "It worked when I tried it" โ‰  comprehensive + +Automated tests are systematic. They run the same way every time. + +**"Deleting X hours of work is wasteful"** + +Sunk cost fallacy. The time is already gone. Your choice now: +- Delete and rewrite with TDD (X more hours, high confidence) +- Keep it and add tests after (30 min, low confidence, likely bugs) + +The "waste" is keeping code you can't trust. Working code without real tests is technical debt. + +**"TDD is dogmatic, being pragmatic means adapting"** + +TDD IS pragmatic: +- Finds bugs before commit (faster than debugging after) +- Prevents regressions (tests catch breaks immediately) +- Documents behavior (tests show how to use code) +- Enables refactoring (change freely, tests catch breaks) + +"Pragmatic" shortcuts = debugging in production = slower. + +**"Tests after achieve the same goals - it's spirit not ritual"** + +No. Tests-after answer "What does this do?" Tests-first answer "What should this do?" + +Tests-after are biased by your implementation. You test what you built, not what's required. You verify remembered edge cases, not discovered ones. + +Tests-first force edge case discovery before implementing. Tests-after verify you remembered everything (you didn't). + +30 minutes of tests after โ‰  TDD. You get coverage, lose proof tests work. + +## Common Rationalizations + +| Excuse | Reality | +|--------|---------| +| "Too simple to test" | Simple code breaks. Test takes 30 seconds. | +| "I'll test after" | Tests passing immediately prove nothing. | +| "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" | +| "Already manually tested" | Ad-hoc โ‰  systematic. No record, can't re-run. | +| "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is technical debt. | +| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. | +| "Need to explore first" | Fine. Throw away exploration, start with TDD. | +| "Test hard = design unclear" | Listen to test. Hard to test = hard to use. | +| "TDD will slow me down" | TDD faster than debugging. Pragmatic = test-first. | +| "Manual test faster" | Manual doesn't prove edge cases. You'll re-test every change. | +| "Existing code has no tests" | You're improving it. Add tests for existing code. | + +## Red Flags - STOP and Start Over + +- Code before test +- Test after implementation +- Test passes immediately +- Can't explain why test failed +- Tests added "later" +- Rationalizing "just this once" +- "I already manually tested it" +- "Tests after achieve the same purpose" +- "It's about spirit not ritual" +- "Keep as reference" or "adapt existing code" +- "Already spent X hours, deleting is wasteful" +- "TDD is dogmatic, I'm being pragmatic" +- "This is different because..." + +**All of these mean: Delete code. Start over with TDD.** + +## Example: Bug Fix + +**Bug:** Empty email accepted + +**RED** +```typescript +test('rejects empty email', async () => { + const result = await submitForm({ email: '' }); + expect(result.error).toBe('Email required'); +}); +``` + +**Verify RED** +```bash +$ npm test +FAIL: expected 'Email required', got undefined +``` + +**GREEN** +```typescript +function submitForm(data: FormData) { + if (!data.email?.trim()) { + return { error: 'Email required' }; + } + // ... +} +``` + +**Verify GREEN** +```bash +$ npm test +PASS +``` + +**REFACTOR** +Extract validation for multiple fields if needed. + +## Verification Checklist + +Before marking work complete: + +- [ ] Every new function/method has a test +- [ ] Watched each test fail before implementing +- [ ] Each test failed for expected reason (feature missing, not typo) +- [ ] Wrote minimal code to pass each test +- [ ] All tests pass +- [ ] Output pristine (no errors, warnings) +- [ ] Tests use real code (mocks only if unavoidable) +- [ ] Edge cases and errors covered + +Can't check all boxes? You skipped TDD. Start over. + +## When Stuck + +| Problem | Solution | +|---------|----------| +| Don't know how to test | Write wished-for API. Write assertion first. Ask your human partner. | +| Test too complicated | Design too complicated. Simplify interface. | +| Must mock everything | Code too coupled. Use dependency injection. | +| Test setup huge | Extract helpers. Still complex? Simplify design. | + +## Debugging Integration + +Bug found? Write failing test reproducing it. Follow TDD cycle. Test proves fix and prevents regression. + +Never fix bugs without a test. + +## Testing Anti-Patterns + +When adding mocks or test utilities, read @testing-anti-patterns.md to avoid common pitfalls: +- Testing mock behavior instead of real behavior +- Adding test-only methods to production classes +- Mocking without understanding dependencies + +## Final Rule + +``` +Production code โ†’ test exists and failed first +Otherwise โ†’ not TDD +``` + +No exceptions without your human partner's permission. diff --git a/.agents/skills/test-driven-development/testing-anti-patterns.md b/.agents/skills/test-driven-development/testing-anti-patterns.md new file mode 100644 index 0000000..e77ab6b --- /dev/null +++ b/.agents/skills/test-driven-development/testing-anti-patterns.md @@ -0,0 +1,299 @@ +# Testing Anti-Patterns + +**Load this reference when:** writing or changing tests, adding mocks, or tempted to add test-only methods to production code. + +## Overview + +Tests must verify real behavior, not mock behavior. Mocks are a means to isolate, not the thing being tested. + +**Core principle:** Test what the code does, not what the mocks do. + +**Following strict TDD prevents these anti-patterns.** + +## The Iron Laws + +``` +1. NEVER test mock behavior +2. NEVER add test-only methods to production classes +3. NEVER mock without understanding dependencies +``` + +## Anti-Pattern 1: Testing Mock Behavior + +**The violation:** +```typescript +// โŒ BAD: Testing that the mock exists +test('renders sidebar', () => { + render(); + expect(screen.getByTestId('sidebar-mock')).toBeInTheDocument(); +}); +``` + +**Why this is wrong:** +- You're verifying the mock works, not that the component works +- Test passes when mock is present, fails when it's not +- Tells you nothing about real behavior + +**your human partner's correction:** "Are we testing the behavior of a mock?" + +**The fix:** +```typescript +// โœ… GOOD: Test real component or don't mock it +test('renders sidebar', () => { + render(); // Don't mock sidebar + expect(screen.getByRole('navigation')).toBeInTheDocument(); +}); + +// OR if sidebar must be mocked for isolation: +// Don't assert on the mock - test Page's behavior with sidebar present +``` + +### Gate Function + +``` +BEFORE asserting on any mock element: + Ask: "Am I testing real component behavior or just mock existence?" + + IF testing mock existence: + STOP - Delete the assertion or unmock the component + + Test real behavior instead +``` + +## Anti-Pattern 2: Test-Only Methods in Production + +**The violation:** +```typescript +// โŒ BAD: destroy() only used in tests +class Session { + async destroy() { // Looks like production API! + await this._workspaceManager?.destroyWorkspace(this.id); + // ... cleanup + } +} + +// In tests +afterEach(() => session.destroy()); +``` + +**Why this is wrong:** +- Production class polluted with test-only code +- Dangerous if accidentally called in production +- Violates YAGNI and separation of concerns +- Confuses object lifecycle with entity lifecycle + +**The fix:** +```typescript +// โœ… GOOD: Test utilities handle test cleanup +// Session has no destroy() - it's stateless in production + +// In test-utils/ +export async function cleanupSession(session: Session) { + const workspace = session.getWorkspaceInfo(); + if (workspace) { + await workspaceManager.destroyWorkspace(workspace.id); + } +} + +// In tests +afterEach(() => cleanupSession(session)); +``` + +### Gate Function + +``` +BEFORE adding any method to production class: + Ask: "Is this only used by tests?" + + IF yes: + STOP - Don't add it + Put it in test utilities instead + + Ask: "Does this class own this resource's lifecycle?" + + IF no: + STOP - Wrong class for this method +``` + +## Anti-Pattern 3: Mocking Without Understanding + +**The violation:** +```typescript +// โŒ BAD: Mock breaks test logic +test('detects duplicate server', () => { + // Mock prevents config write that test depends on! + vi.mock('ToolCatalog', () => ({ + discoverAndCacheTools: vi.fn().mockResolvedValue(undefined) + })); + + await addServer(config); + await addServer(config); // Should throw - but won't! +}); +``` + +**Why this is wrong:** +- Mocked method had side effect test depended on (writing config) +- Over-mocking to "be safe" breaks actual behavior +- Test passes for wrong reason or fails mysteriously + +**The fix:** +```typescript +// โœ… GOOD: Mock at correct level +test('detects duplicate server', () => { + // Mock the slow part, preserve behavior test needs + vi.mock('MCPServerManager'); // Just mock slow server startup + + await addServer(config); // Config written + await addServer(config); // Duplicate detected โœ“ +}); +``` + +### Gate Function + +``` +BEFORE mocking any method: + STOP - Don't mock yet + + 1. Ask: "What side effects does the real method have?" + 2. Ask: "Does this test depend on any of those side effects?" + 3. Ask: "Do I fully understand what this test needs?" + + IF depends on side effects: + Mock at lower level (the actual slow/external operation) + OR use test doubles that preserve necessary behavior + NOT the high-level method the test depends on + + IF unsure what test depends on: + Run test with real implementation FIRST + Observe what actually needs to happen + THEN add minimal mocking at the right level + + Red flags: + - "I'll mock this to be safe" + - "This might be slow, better mock it" + - Mocking without understanding the dependency chain +``` + +## Anti-Pattern 4: Incomplete Mocks + +**The violation:** +```typescript +// โŒ BAD: Partial mock - only fields you think you need +const mockResponse = { + status: 'success', + data: { userId: '123', name: 'Alice' } + // Missing: metadata that downstream code uses +}; + +// Later: breaks when code accesses response.metadata.requestId +``` + +**Why this is wrong:** +- **Partial mocks hide structural assumptions** - You only mocked fields you know about +- **Downstream code may depend on fields you didn't include** - Silent failures +- **Tests pass but integration fails** - Mock incomplete, real API complete +- **False confidence** - Test proves nothing about real behavior + +**The Iron Rule:** Mock the COMPLETE data structure as it exists in reality, not just fields your immediate test uses. + +**The fix:** +```typescript +// โœ… GOOD: Mirror real API completeness +const mockResponse = { + status: 'success', + data: { userId: '123', name: 'Alice' }, + metadata: { requestId: 'req-789', timestamp: 1234567890 } + // All fields real API returns +}; +``` + +### Gate Function + +``` +BEFORE creating mock responses: + Check: "What fields does the real API response contain?" + + Actions: + 1. Examine actual API response from docs/examples + 2. Include ALL fields system might consume downstream + 3. Verify mock matches real response schema completely + + Critical: + If you're creating a mock, you must understand the ENTIRE structure + Partial mocks fail silently when code depends on omitted fields + + If uncertain: Include all documented fields +``` + +## Anti-Pattern 5: Integration Tests as Afterthought + +**The violation:** +``` +โœ… Implementation complete +โŒ No tests written +"Ready for testing" +``` + +**Why this is wrong:** +- Testing is part of implementation, not optional follow-up +- TDD would have caught this +- Can't claim complete without tests + +**The fix:** +``` +TDD cycle: +1. Write failing test +2. Implement to pass +3. Refactor +4. THEN claim complete +``` + +## When Mocks Become Too Complex + +**Warning signs:** +- Mock setup longer than test logic +- Mocking everything to make test pass +- Mocks missing methods real components have +- Test breaks when mock changes + +**your human partner's question:** "Do we need to be using a mock here?" + +**Consider:** Integration tests with real components often simpler than complex mocks + +## TDD Prevents These Anti-Patterns + +**Why TDD helps:** +1. **Write test first** โ†’ Forces you to think about what you're actually testing +2. **Watch it fail** โ†’ Confirms test tests real behavior, not mocks +3. **Minimal implementation** โ†’ No test-only methods creep in +4. **Real dependencies** โ†’ You see what the test actually needs before mocking + +**If you're testing mock behavior, you violated TDD** - you added mocks without watching test fail against real code first. + +## Quick Reference + +| Anti-Pattern | Fix | +|--------------|-----| +| Assert on mock elements | Test real component or unmock it | +| Test-only methods in production | Move to test utilities | +| Mock without understanding | Understand dependencies first, mock minimally | +| Incomplete mocks | Mirror real API completely | +| Tests as afterthought | TDD - tests first | +| Over-complex mocks | Consider integration tests | + +## Red Flags + +- Assertion checks for `*-mock` test IDs +- Methods only called in test files +- Mock setup is >50% of test +- Test fails when you remove mock +- Can't explain why mock is needed +- Mocking "just to be safe" + +## The Bottom Line + +**Mocks are tools to isolate, not things to test.** + +If TDD reveals you're testing mock behavior, you've gone wrong. + +Fix: Test real behavior or question why you're mocking at all. diff --git a/.claude/skills/add-dart-lint-validation-rule b/.claude/skills/add-dart-lint-validation-rule new file mode 120000 index 0000000..ef428b8 --- /dev/null +++ b/.claude/skills/add-dart-lint-validation-rule @@ -0,0 +1 @@ +../../.agents/skills/add-dart-lint-validation-rule \ No newline at end of file diff --git a/.claude/skills/dart-best-practices b/.claude/skills/dart-best-practices new file mode 120000 index 0000000..0101819 --- /dev/null +++ b/.claude/skills/dart-best-practices @@ -0,0 +1 @@ +../../.agents/skills/dart-best-practices \ No newline at end of file diff --git a/.claude/skills/dart-cli-app-best-practices b/.claude/skills/dart-cli-app-best-practices new file mode 120000 index 0000000..137cb0a --- /dev/null +++ b/.claude/skills/dart-cli-app-best-practices @@ -0,0 +1 @@ +../../.agents/skills/dart-cli-app-best-practices \ No newline at end of file diff --git a/.claude/skills/dart-doc-validation b/.claude/skills/dart-doc-validation new file mode 120000 index 0000000..4f6ef3c --- /dev/null +++ b/.claude/skills/dart-doc-validation @@ -0,0 +1 @@ +../../.agents/skills/dart-doc-validation \ No newline at end of file diff --git a/.claude/skills/dart-matcher-best-practices b/.claude/skills/dart-matcher-best-practices new file mode 120000 index 0000000..96a8d28 --- /dev/null +++ b/.claude/skills/dart-matcher-best-practices @@ -0,0 +1 @@ +../../.agents/skills/dart-matcher-best-practices \ No newline at end of file diff --git a/.claude/skills/dart-modern-features b/.claude/skills/dart-modern-features new file mode 120000 index 0000000..6b32dbf --- /dev/null +++ b/.claude/skills/dart-modern-features @@ -0,0 +1 @@ +../../.agents/skills/dart-modern-features \ No newline at end of file diff --git a/.claude/skills/dart-package-maintenance b/.claude/skills/dart-package-maintenance new file mode 120000 index 0000000..1f96019 --- /dev/null +++ b/.claude/skills/dart-package-maintenance @@ -0,0 +1 @@ +../../.agents/skills/dart-package-maintenance \ No newline at end of file diff --git a/.claude/skills/dart-test-fundamentals b/.claude/skills/dart-test-fundamentals new file mode 120000 index 0000000..669db53 --- /dev/null +++ b/.claude/skills/dart-test-fundamentals @@ -0,0 +1 @@ +../../.agents/skills/dart-test-fundamentals \ No newline at end of file diff --git a/.claude/skills/test-driven-development b/.claude/skills/test-driven-development new file mode 120000 index 0000000..df48f33 --- /dev/null +++ b/.claude/skills/test-driven-development @@ -0,0 +1 @@ +../../.agents/skills/test-driven-development \ No newline at end of file diff --git a/.gitignore b/.gitignore index c8acc58..2e57a3a 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,8 @@ packages/*/coverage/* !packages/*/coverage/lcov.info # Keep .claude directory -.claude/ +.claude/* +!.claude/skills/ CLAUDE.md .idea