From ee20fa77922e3c252868ad12e18fff678a45069b Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Wed, 17 Jun 2026 16:49:14 -0400 Subject: [PATCH 01/25] feat: Set up agent skills management architecture - Add dart_skills_lint dependency and configuration - Create .agents/skills and skills directories with documentation - Set up isolated third_party skills installation for remote skills - Symlink remote skills into .agents/skills - Add validate_skills_test.dart for CI integration --- .../.agents/skills/.gitignore | 11 + .../.agents/skills/README.md | 8 + .../.agents/skills/dart-add-unit-test | 1 + .../.agents/skills/dart-collect-coverage | 1 + .../.agents/skills/dart-generate-test-mocks | 1 + .../.agents/skills/dart-run-static-analysis | 1 + .../skills/flutter-add-integration-test | 1 + .../.agents/skills/grill-me | 1 + .../.agents/skills/receiving-code-review | 1 + .../dart_skills_lint.yaml | 9 + .../camera_android_camerax/pubspec.yaml | 5 + .../camera_android_camerax/skills/README.md | 3 + .../test/validate_skills_test.dart | 27 +++ third_party/README.md | 23 ++ third_party/anthropics-skills/LICENSE | 1 + .../anthropics-skills/skills-lock.json | 11 + .../skills/dart-add-unit-test/SKILL.md | 122 ++++++++++ .../skills/dart-collect-coverage/SKILL.md | 141 ++++++++++++ .../skills/dart-generate-test-mocks/SKILL.md | 155 +++++++++++++ .../skills/dart-run-static-analysis/SKILL.md | 104 +++++++++ third_party/dart-lang-skills/LICENSE | 26 +++ third_party/dart-lang-skills/skills-lock.json | 29 +++ .../flutter-add-integration-test/SKILL.md | 163 ++++++++++++++ third_party/flutter-skills/LICENSE | 26 +++ third_party/flutter-skills/skills-lock.json | 11 + .../.agents/skills/grill-me/SKILL.md | 7 + third_party/mattpocock-skills/LICENSE | 21 ++ .../mattpocock-skills/skills-lock.json | 11 + .../skills/receiving-code-review/SKILL.md | 213 ++++++++++++++++++ third_party/obra-superpowers/LICENSE | 21 ++ third_party/obra-superpowers/skills-lock.json | 11 + 31 files changed, 1166 insertions(+) create mode 100644 packages/camera/camera_android_camerax/.agents/skills/.gitignore create mode 100644 packages/camera/camera_android_camerax/.agents/skills/README.md create mode 120000 packages/camera/camera_android_camerax/.agents/skills/dart-add-unit-test create mode 120000 packages/camera/camera_android_camerax/.agents/skills/dart-collect-coverage create mode 120000 packages/camera/camera_android_camerax/.agents/skills/dart-generate-test-mocks create mode 120000 packages/camera/camera_android_camerax/.agents/skills/dart-run-static-analysis create mode 120000 packages/camera/camera_android_camerax/.agents/skills/flutter-add-integration-test create mode 120000 packages/camera/camera_android_camerax/.agents/skills/grill-me create mode 120000 packages/camera/camera_android_camerax/.agents/skills/receiving-code-review create mode 100644 packages/camera/camera_android_camerax/dart_skills_lint.yaml create mode 100644 packages/camera/camera_android_camerax/skills/README.md create mode 100644 packages/camera/camera_android_camerax/test/validate_skills_test.dart create mode 100644 third_party/README.md create mode 100644 third_party/anthropics-skills/LICENSE create mode 100644 third_party/anthropics-skills/skills-lock.json create mode 100644 third_party/dart-lang-skills/.agents/skills/dart-add-unit-test/SKILL.md create mode 100644 third_party/dart-lang-skills/.agents/skills/dart-collect-coverage/SKILL.md create mode 100644 third_party/dart-lang-skills/.agents/skills/dart-generate-test-mocks/SKILL.md create mode 100644 third_party/dart-lang-skills/.agents/skills/dart-run-static-analysis/SKILL.md create mode 100644 third_party/dart-lang-skills/LICENSE create mode 100644 third_party/dart-lang-skills/skills-lock.json create mode 100644 third_party/flutter-skills/.agents/skills/flutter-add-integration-test/SKILL.md create mode 100644 third_party/flutter-skills/LICENSE create mode 100644 third_party/flutter-skills/skills-lock.json create mode 100644 third_party/mattpocock-skills/.agents/skills/grill-me/SKILL.md create mode 100644 third_party/mattpocock-skills/LICENSE create mode 100644 third_party/mattpocock-skills/skills-lock.json create mode 100644 third_party/obra-superpowers/.agents/skills/receiving-code-review/SKILL.md create mode 100644 third_party/obra-superpowers/LICENSE create mode 100644 third_party/obra-superpowers/skills-lock.json diff --git a/packages/camera/camera_android_camerax/.agents/skills/.gitignore b/packages/camera/camera_android_camerax/.agents/skills/.gitignore new file mode 100644 index 000000000000..bf61c92cbf22 --- /dev/null +++ b/packages/camera/camera_android_camerax/.agents/skills/.gitignore @@ -0,0 +1,11 @@ +# Ignore everything by default +* + +# Un-ignore specific checked-in skills +# (Add specific contributor skills here as they are created) + +# Keep essential configuration and docs +!.gitignore +!README.md +!ignore.json +!flutter_skills_ignore.json diff --git a/packages/camera/camera_android_camerax/.agents/skills/README.md b/packages/camera/camera_android_camerax/.agents/skills/README.md new file mode 100644 index 000000000000..2aee515d1fdf --- /dev/null +++ b/packages/camera/camera_android_camerax/.agents/skills/README.md @@ -0,0 +1,8 @@ +# Agent Skills + +This directory contains skills intended for repository maintainers and contributors. Local, checked-in skills are evaluated by `dart_skills_lint` and should be configured to prevent publishing to pub.dev. + +**Note on Remotely Managed Skills:** +Skills that are remotely defined and managed using `npx skills` should **not** be installed directly into this directory. Instead, they must be installed into the repository root's `third_party/` directory to comply with third-party code policies. Once installed there, they should be symlinked into this directory. + +Please see the `third_party/README.md` file at the root of the repository for specific rules and instructions on adding new remote skills. diff --git a/packages/camera/camera_android_camerax/.agents/skills/dart-add-unit-test b/packages/camera/camera_android_camerax/.agents/skills/dart-add-unit-test new file mode 120000 index 000000000000..75f7c8152662 --- /dev/null +++ b/packages/camera/camera_android_camerax/.agents/skills/dart-add-unit-test @@ -0,0 +1 @@ +../../../../../third_party/dart-lang-skills/.agents/skills/dart-add-unit-test \ No newline at end of file diff --git a/packages/camera/camera_android_camerax/.agents/skills/dart-collect-coverage b/packages/camera/camera_android_camerax/.agents/skills/dart-collect-coverage new file mode 120000 index 000000000000..962be2a216f2 --- /dev/null +++ b/packages/camera/camera_android_camerax/.agents/skills/dart-collect-coverage @@ -0,0 +1 @@ +../../../../../third_party/dart-lang-skills/.agents/skills/dart-collect-coverage \ No newline at end of file diff --git a/packages/camera/camera_android_camerax/.agents/skills/dart-generate-test-mocks b/packages/camera/camera_android_camerax/.agents/skills/dart-generate-test-mocks new file mode 120000 index 000000000000..30f6449bd381 --- /dev/null +++ b/packages/camera/camera_android_camerax/.agents/skills/dart-generate-test-mocks @@ -0,0 +1 @@ +../../../../../third_party/dart-lang-skills/.agents/skills/dart-generate-test-mocks \ No newline at end of file diff --git a/packages/camera/camera_android_camerax/.agents/skills/dart-run-static-analysis b/packages/camera/camera_android_camerax/.agents/skills/dart-run-static-analysis new file mode 120000 index 000000000000..b2d645ab0cd3 --- /dev/null +++ b/packages/camera/camera_android_camerax/.agents/skills/dart-run-static-analysis @@ -0,0 +1 @@ +../../../../../third_party/dart-lang-skills/.agents/skills/dart-run-static-analysis \ No newline at end of file diff --git a/packages/camera/camera_android_camerax/.agents/skills/flutter-add-integration-test b/packages/camera/camera_android_camerax/.agents/skills/flutter-add-integration-test new file mode 120000 index 000000000000..7f968348c061 --- /dev/null +++ b/packages/camera/camera_android_camerax/.agents/skills/flutter-add-integration-test @@ -0,0 +1 @@ +../../../../../third_party/flutter-skills/.agents/skills/flutter-add-integration-test \ No newline at end of file diff --git a/packages/camera/camera_android_camerax/.agents/skills/grill-me b/packages/camera/camera_android_camerax/.agents/skills/grill-me new file mode 120000 index 000000000000..f1407c690beb --- /dev/null +++ b/packages/camera/camera_android_camerax/.agents/skills/grill-me @@ -0,0 +1 @@ +../../../../../third_party/mattpocock-skills/.agents/skills/grill-me \ No newline at end of file diff --git a/packages/camera/camera_android_camerax/.agents/skills/receiving-code-review b/packages/camera/camera_android_camerax/.agents/skills/receiving-code-review new file mode 120000 index 000000000000..1710c02a9776 --- /dev/null +++ b/packages/camera/camera_android_camerax/.agents/skills/receiving-code-review @@ -0,0 +1 @@ +../../../../../third_party/obra-superpowers/.agents/skills/receiving-code-review \ No newline at end of file diff --git a/packages/camera/camera_android_camerax/dart_skills_lint.yaml b/packages/camera/camera_android_camerax/dart_skills_lint.yaml new file mode 100644 index 000000000000..d7ecc80bc610 --- /dev/null +++ b/packages/camera/camera_android_camerax/dart_skills_lint.yaml @@ -0,0 +1,9 @@ +dart_skills_lint: + rules: + check-relative-paths: error + check-trailing-whitespace: error + directories: + - path: "skills" + - path: ".agents/skills" + rules: + prevent-skills-sh-publishing: error diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index e5fbd9bf008e..61eaf7a37522 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -27,9 +27,14 @@ dependencies: dev_dependencies: build_runner: ^2.2.0 + dart_skills_lint: + git: + url: https://github.com/flutter/skills.git + path: tool/dart_skills_lint flutter_test: sdk: flutter leak_tracker_flutter_testing: any + logging: ^1.2.0 mockito: ^5.4.4 pigeon: ^26.1.4 diff --git a/packages/camera/camera_android_camerax/skills/README.md b/packages/camera/camera_android_camerax/skills/README.md new file mode 100644 index 000000000000..738112ec4fe1 --- /dev/null +++ b/packages/camera/camera_android_camerax/skills/README.md @@ -0,0 +1,3 @@ +# Published Skills + +This directory contains AI agent skills that are intended to be published to pub.dev. diff --git a/packages/camera/camera_android_camerax/test/validate_skills_test.dart b/packages/camera/camera_android_camerax/test/validate_skills_test.dart new file mode 100644 index 000000000000..e7bd4e19c292 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/validate_skills_test.dart @@ -0,0 +1,27 @@ +import 'dart:async'; + +import 'package:dart_skills_lint/dart_skills_lint.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:logging/logging.dart'; + +void main() { + test('Validate Agent Skills', () async { + final Level oldLevel = Logger.root.level; + Logger.root.level = Level.ALL; + final StreamSubscription subscription = Logger.root.onRecord.listen((record) { + debugPrint(record.message); + }); + + try { + final Configuration config = await ConfigParser.loadConfig(); + final bool isValid = await validateSkills( + config: config, + ); + expect(isValid, isTrue, reason: 'Skills validation failed. See above for details.'); + } finally { + Logger.root.level = oldLevel; + await subscription.cancel(); + } + }); +} diff --git a/third_party/README.md b/third_party/README.md new file mode 100644 index 000000000000..7f619e213993 --- /dev/null +++ b/third_party/README.md @@ -0,0 +1,23 @@ +# Third-Party Agent Skills + +To comply with third-party code policies, all remotely managed agent skills (installed via `npx skills`) must be isolated in this `third_party/` directory before being symlinked to specific project `.agents/skills` directories. + +## Rules for Adding New Remote Skills + +When adding a new remote skill, you must follow this exact structure and process: + +1. **Folder per Repository:** Create a new folder under `third_party/` named uniquely after the GitHub repository the skill originates from (e.g., `third_party/dart-lang-skills`). + +2. **Installation:** Run the `npx skills` command from *within* that new repository folder. This ensures the `skills-lock.json` file is correctly generated inside the subfolder. All `skills-lock.json` files must be generated by the CLI, not manually crafted. + ```bash + cd third_party/ + npx skills add --skill -y + ``` + +3. **License Requirement:** You **must** download and retain the original `LICENSE` file from the remote repository and place it directly inside the `third_party/` subfolder. + +4. **Symlinking:** Once installed, symlink the newly downloaded skill into the appropriate project's `.agents/skills` directory using a relative symlink. + ```bash + cd packages//.agents/skills + ln -s ../../../../../third_party//.agents/skills/ + ``` diff --git a/third_party/anthropics-skills/LICENSE b/third_party/anthropics-skills/LICENSE new file mode 100644 index 000000000000..1becba2bb0a8 --- /dev/null +++ b/third_party/anthropics-skills/LICENSE @@ -0,0 +1 @@ +404: Not Found \ No newline at end of file diff --git a/third_party/anthropics-skills/skills-lock.json b/third_party/anthropics-skills/skills-lock.json new file mode 100644 index 000000000000..1cbfc01ccc6b --- /dev/null +++ b/third_party/anthropics-skills/skills-lock.json @@ -0,0 +1,11 @@ +{ + "version": 1, + "skills": { + "skill-creator": { + "source": "anthropics/skills", + "sourceType": "github", + "skillPath": "skills/skill-creator/SKILL.md", + "computedHash": "5ea13a6d9f0d4bb694405d79acd00cadec0d21bb138c4dd10fcf3c500cb835c2" + } + } +} diff --git a/third_party/dart-lang-skills/.agents/skills/dart-add-unit-test/SKILL.md b/third_party/dart-lang-skills/.agents/skills/dart-add-unit-test/SKILL.md new file mode 100644 index 000000000000..dc27083f3113 --- /dev/null +++ b/third_party/dart-lang-skills/.agents/skills/dart-add-unit-test/SKILL.md @@ -0,0 +1,122 @@ +--- +name: dart-add-unit-test +description: Write and organize unit tests for functions, methods, and classes using `package:test`. Use when creating new logic or fixing bugs to ensure code remains correct and regression-free. +metadata: + model: models/gemini-3.1-pro-preview + last_modified: Fri, 24 Apr 2026 15:07:58 GMT +--- +# Testing Dart and Flutter Applications + +## Contents +- [Structuring Test Files](#structuring-test-files) +- [Writing Tests](#writing-tests) +- [Executing Tests](#executing-tests) +- [Test Implementation Workflow](#test-implementation-workflow) +- [Examples](#examples) + +## Structuring Test Files +Organize test files to mirror the `lib` directory structure to maintain predictability. + +* Place all test code within the `test` directory at the root of the package. +* Append `_test.dart` to the end of all test file names (e.g., `lib/src/utils.dart` should be tested in `test/src/utils_test.dart`). +* If writing integration tests, place them in an `integration_test` directory at the root of the package. + +## Writing Tests +Utilize `package:test` as the standard testing library for Dart applications. + +* Import `package:test/test.dart` (or `package:flutter_test/flutter_test.dart` for Flutter). +* Group related tests using the `group()` function to provide shared context. +* Define individual test cases using the `test()` function. +* Validate outcomes using the `expect()` function alongside matchers (e.g., `equals()`, `isTrue`, `throwsA()`). +* Write asynchronous tests using standard `async`/`await` syntax. The test runner automatically waits for the `Future` to complete. +* Manage test setup and teardown using `setUp()` and `tearDown()` callbacks. +* If testing code that relies on dependency injection, use `package:mockito` alongside `package:test` to generate mock objects, configure fixed scenarios, and verify interactions. + +## Executing Tests +Select the appropriate test runner based on the project type and test location. + +* If working on a pure Dart project, execute tests using the `dart test` command. +* If working on a Flutter project, execute tests using the `flutter test` command. +* If running integration tests, explicitly specify the directory path, as the default runner ignores it: `dart test integration_test` or `flutter test integration_test`. + +## Test Implementation Workflow + +Follow this sequential workflow when implementing new test suites. Copy the checklist to track your progress. + +### Task Progress +- [ ] 1. Create the test file in the `test/` directory, ensuring the `_test.dart` suffix. +- [ ] 2. Import `package:test/test.dart` and the target library. +- [ ] 3. Define a `main()` function. +- [ ] 4. Initialize shared resources or mocks using `setUp()`. +- [ ] 5. Write `test()` cases grouped by functionality using `group()`. +- [ ] 6. Execute the test suite using the appropriate CLI command. +- [ ] 7. **Feedback Loop**: Run test -> Review stack trace for failures -> Fix implementation or assertions -> Re-run until passing. + +## Examples + +### Standard Unit Test Suite +Demonstrates grouping, setup, synchronous, and asynchronous testing. + +```dart +import 'package:test/test.dart'; +import 'package:my_package/calculator.dart'; + +void main() { + group('Calculator', () { + late Calculator calc; + + setUp(() { + calc = Calculator(); + }); + + test('adds two numbers correctly', () { + expect(calc.add(2, 3), equals(5)); + }); + + test('handles asynchronous operations', () async { + final result = await calc.fetchRemoteValue(); + expect(result, isNotNull); + expect(result, greaterThan(0)); + }); + }); +} +``` + +### Mocking with Mockito +Demonstrates configuring a mock object for dependency injection testing. + +```dart +import 'package:test/test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:mockito/annotations.dart'; +import 'package:my_package/api_client.dart'; +import 'package:my_package/data_service.dart'; + +// Generate the mock using build_runner: dart run build_runner build +@GenerateNiceMocks([MockSpec()]) +import 'data_service_test.mocks.dart'; + +void main() { + group('DataService', () { + late MockApiClient mockApiClient; + late DataService dataService; + + setUp(() { + mockApiClient = MockApiClient(); + dataService = DataService(apiClient: mockApiClient); + }); + + test('returns parsed data on successful API call', () async { + // Configure the mock + when(mockApiClient.get('/data')).thenAnswer((_) async => '{"id": 1}'); + + // Execute the system under test + final result = await dataService.fetchData(); + + // Verify outcomes and interactions + expect(result.id, equals(1)); + verify(mockApiClient.get('/data')).called(1); + }); + }); +} +``` diff --git a/third_party/dart-lang-skills/.agents/skills/dart-collect-coverage/SKILL.md b/third_party/dart-lang-skills/.agents/skills/dart-collect-coverage/SKILL.md new file mode 100644 index 000000000000..60dad77533dc --- /dev/null +++ b/third_party/dart-lang-skills/.agents/skills/dart-collect-coverage/SKILL.md @@ -0,0 +1,141 @@ +--- +name: dart-collect-coverage +description: Collect coverage using the coverage packge and create an LCOV report +metadata: + model: models/gemini-3.1-pro-preview + last_modified: Fri, 24 Apr 2026 15:14:32 GMT +--- +# Implementing Dart and Flutter Test Coverage + +## Contents +- [Testing Fundamentals](#testing-fundamentals) +- [Coverage Directives](#coverage-directives) +- [Workflow: Configuring and Generating Coverage Reports](#workflow-configuring-and-generating-coverage-reports) +- [Workflow: Advanced Manual Coverage Collection](#workflow-advanced-manual-coverage-collection) +- [Examples](#examples) + +## Testing Fundamentals + +Structure your test suites using the standard Dart testing paradigms. Use `package:test` for Dart projects and `flutter_test` for Flutter projects. + +- **Unit Tests:** Verify individual functions, methods, or classes. +- **Component/Widget Tests:** Verify component behavior, layout, and interaction using mock objects (`package:mockito`). +- **Integration Tests:** Verify entire app flows on simulated or real devices. + +## Coverage Directives + +Exclude specific lines, blocks, or entire files from coverage metrics using inline comments. Pass the `--check-ignore` flag during formatting to enforce these directives. + +- Ignore a single line: `// coverage:ignore-line` +- Ignore a block of code: `// coverage:ignore-start` and `// coverage:ignore-end` +- Ignore an entire file: `// coverage:ignore-file` + +## Workflow: Configuring and Generating Coverage Reports + +Follow this sequential workflow to add the coverage package, execute tests, and generate an LCOV report. + +**Task Progress Checklist:** +- [ ] 1. Add `coverage` as a `dev_dependency`. +- [ ] 2. Execute the automated coverage script. +- [ ] 3. Validate the LCOV output. + +### 1. Add Dependencies +Add the `coverage` package as a `dev_dependency` to your project. Do not add it to standard dependencies. + +If working in a standard Dart project: +```bash +dart pub add dev:coverage +``` + +If working in a Flutter project: +```bash +flutter pub add dev:coverage +``` + +### 2. Collect Coverage and Generate LCOV +Use the bundled `test_with_coverage` script. This script automatically runs all tests, collects the JSON coverage data from the Dart VM, and formats it into an LCOV report. + +```bash +dart run coverage:test_with_coverage +``` +*Note: If working within a Dart workspace (monorepo), specify the test directories explicitly (e.g., `dart run coverage:test_with_coverage -- pkgs/foo/test pkgs/bar/test`).* + +### 3. Feedback Loop: Validate Output +**Run validator -> review errors -> fix:** +1. Verify that the `coverage/` directory was created in the project root. +2. Ensure `coverage/coverage.json` (raw data) and `coverage/lcov.info` (formatted report) exist. +3. If coverage is missing for specific files, ensure they are imported and executed by your test files, or add `// coverage:ignore-file` if they are intentionally excluded. + +## Workflow: Advanced Manual Coverage Collection + +If you require granular control over the VM service, isolate pausing, or need branch/function-level coverage, use the manual collection workflow. + +**Task Progress Checklist:** +- [ ] 1. Run tests with VM service enabled. +- [ ] 2. Collect raw JSON coverage. +- [ ] 3. Format JSON to LCOV. + +### 1. Run Tests with VM Service +Execute tests while pausing isolates on exit and exposing the VM service on a specific port (e.g., 8181). + +```bash +dart run --pause-isolates-on-exit --disable-service-auth-codes --enable-vm-service=8181 test & +``` + +### 2. Collect Raw Coverage +Extract the coverage data from the running VM service and output it to a JSON file. + +```bash +dart run coverage:collect_coverage --wait-paused --uri=http://127.0.0.1:8181/ -o coverage/coverage.json --resume-isolates +``` +*Optional: Append `--function-coverage` and `--branch-coverage` to gather deeper metrics (requires Dart VM 2.17.0+).* + +### 3. Format to LCOV +Convert the raw JSON data into the standard LCOV format. + +```bash +dart run coverage:format_coverage --packages=.dart_tool/package_config.json --lcov -i coverage/coverage.json -o coverage/lcov.info --check-ignore +``` + +## Examples + +### Example: `pubspec.yaml` Configuration +Ensure your `pubspec.yaml` reflects the `coverage` package strictly under `dev_dependencies`. + +```yaml +name: my_dart_app +environment: + sdk: ^3.0.0 + +dependencies: + path: ^1.8.0 + +dev_dependencies: + test: ^1.24.0 + coverage: ^1.15.0 +``` + +### Example: Applying Ignore Directives +Use ignore directives to prevent generated code or untestable edge cases from lowering coverage scores. + +```dart +// coverage:ignore-file +import 'package:meta/meta.dart'; + +class SystemConfig { + final String env; + + SystemConfig(this.env); + + // coverage:ignore-start + void legacyInit() { + print('Deprecated initialization'); + } + // coverage:ignore-end + + bool isProduction() { + if (env == 'prod') return true; + return false; // coverage:ignore-line + } +} +``` diff --git a/third_party/dart-lang-skills/.agents/skills/dart-generate-test-mocks/SKILL.md b/third_party/dart-lang-skills/.agents/skills/dart-generate-test-mocks/SKILL.md new file mode 100644 index 000000000000..fcd6d8b5e365 --- /dev/null +++ b/third_party/dart-lang-skills/.agents/skills/dart-generate-test-mocks/SKILL.md @@ -0,0 +1,155 @@ +--- +name: dart-generate-test-mocks +description: Define and generate mock objects for external dependencies using `package:mockito` and `build_runner`. Use when unit testing classes that depend on complex external services like APIs or databases. +metadata: + model: models/gemini-3.1-pro-preview + last_modified: Fri, 24 Apr 2026 15:13:58 GMT +--- +# Testing and Mocking Dart Applications + +## Contents +- [Structuring Code for Testability](#structuring-code-for-testability) +- [Managing Dependencies](#managing-dependencies) +- [Generating Mocks](#generating-mocks) +- [Implementing Unit Tests](#implementing-unit-tests) +- [Workflow: Creating and Running Mocked Tests](#workflow-creating-and-running-mocked-tests) +- [Examples](#examples) + +## Structuring Code for Testability +Design Dart classes to support dependency injection. Isolate complex external dependencies (like API clients or databases) so they can be replaced with mock objects during testing. + +- Inject external services (e.g., `http.Client`) through class constructors. +- Represent URLs strictly as `Uri` objects using `Uri.parse(string)`. +- Utilize Dart's object-oriented features (classes, mixins) to define clear interfaces for external interactions. + +## Managing Dependencies +Configure the `pubspec.yaml` file with the necessary testing and code generation packages. + +- Add runtime dependencies (e.g., `package:http`) using `dart pub add http`. +- Add testing dependencies using `dart pub add dev:test dev:mockito dev:build_runner`. +- Import HTTP libraries with a prefix to avoid namespace collisions: `import 'package:http/http.dart' as http;`. + +## Generating Mocks +Use `package:mockito` and `build_runner` to automatically generate mock classes for fixed scenarios and behavior verification. + +- Always use the `@GenerateNiceMocks` annotation (preferable to `@GenerateMocks` to avoid missing stub exceptions). +- Place the annotation in the test file, passing a list of `MockSpec()` objects. +- Import the generated file using the `.mocks.dart` extension. +- Execute `build_runner` to generate the mock files: `dart run build_runner build`. + +## Implementing Unit Tests +Isolate the system under test using the generated mock objects. Use `package:test` to structure the test suite. + +- **Stubbing:** Configure mock behavior before interacting with the system under test. + - Use `when(mock.method()).thenReturn(value)` for synchronous methods. + - **CRITICAL:** Always use `thenAnswer((_) async => value)` for methods returning a `Future` or `Stream`. Never use `thenReturn` for asynchronous returns. +- **Verification:** Assert that the system under test interacted with the mock object correctly. + - Use `verify(mock.method()).called(1)` to check exact invocation counts. + - Use argument matchers like `any`, `anyNamed`, or `captureAny` for flexible verification. + +## Workflow: Creating and Running Mocked Tests + +Use the following checklist to implement and verify mocked unit tests. + +### Task Progress +- [ ] 1. Identify the external dependency to mock (e.g., `http.Client`). +- [ ] 2. Inject the dependency into the target class constructor. +- [ ] 3. Create a test file (e.g., `target_test.dart`) and add `@GenerateNiceMocks([MockSpec()])`. +- [ ] 4. Add the `part` or `import` directive for the generated `.mocks.dart` file. +- [ ] 5. Run `dart run build_runner build` to generate the mock classes. +- [ ] 6. Write the test cases using `group()` and `test()`. +- [ ] 7. Stub required behaviors using `when()`. +- [ ] 8. Execute the target method. +- [ ] 9. Verify interactions using `verify()` and assert outcomes using `expect()`. +- [ ] 10. Run the test suite using `dart test`. + +### Feedback Loop: Test Failures +If tests fail or `build_runner` encounters errors: +1. **Run validator:** Execute `dart test` or `dart run build_runner build`. +2. **Review errors:** Check for missing stubs, mismatched argument matchers, or syntax errors in the generated files. +3. **Fix:** + - If a mock method throws an unexpected null error, ensure you used `@GenerateNiceMocks`. + - If an async stub throws an `ArgumentError`, change `thenReturn` to `thenAnswer`. + - If `build_runner` fails, ensure the `.mocks.dart` import matches the file name exactly. +4. Repeat until all tests pass. + +## Examples + +### High-Fidelity Mocking and Testing Example + +**1. System Under Test (`lib/api_service.dart`)** +```dart +import 'dart:convert'; +import 'package:http/http.dart' as http; + +class ApiService { + final http.Client client; + + ApiService(this.client); + + Future fetchData(String urlString) async { + final uri = Uri.parse(urlString); + final response = await client.get(uri); + + if (response.statusCode == 200) { + return jsonDecode(response.body)['data']; + } else { + throw Exception('Failed to load data'); + } + } +} +``` + +**2. Test Implementation (`test/api_service_test.dart`)** +```dart +import 'package:test/test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:http/http.dart' as http; +import 'package:my_app/api_service.dart'; + +// Generate the mock class for http.Client +@GenerateNiceMocks([MockSpec()]) +import 'api_service_test.mocks.dart'; + +void main() { + group('ApiService', () { + late ApiService apiService; + late MockClient mockHttpClient; + + setUp(() { + mockHttpClient = MockClient(); + apiService = ApiService(mockHttpClient); + }); + + test('returns data if the http call completes successfully', () async { + // Arrange: Stub the async HTTP GET request using thenAnswer + when(mockHttpClient.get(any)).thenAnswer( + (_) async => http.Response('{"data": "Success"}', 200), + ); + + // Act + final result = await apiService.fetchData('https://api.example.com/data'); + + // Assert + expect(result, 'Success'); + + // Verify the mock was called with the correct Uri + verify(mockHttpClient.get(Uri.parse('https://api.example.com/data'))).called(1); + }); + + test('throws an exception if the http call completes with an error', () { + // Arrange + when(mockHttpClient.get(any)).thenAnswer( + (_) async => http.Response('Not Found', 404), + ); + + // Act & Assert + expect( + apiService.fetchData('https://api.example.com/data'), + throwsException, + ); + }); + }); +} +``` diff --git a/third_party/dart-lang-skills/.agents/skills/dart-run-static-analysis/SKILL.md b/third_party/dart-lang-skills/.agents/skills/dart-run-static-analysis/SKILL.md new file mode 100644 index 000000000000..27ca6546684a --- /dev/null +++ b/third_party/dart-lang-skills/.agents/skills/dart-run-static-analysis/SKILL.md @@ -0,0 +1,104 @@ +--- +name: dart-run-static-analysis +description: Execute `dart analyze` to identify warnings and errors, and use `dart fix --apply` to automatically resolve mechanical lint issues. Use during development to ensure code quality and before committing changes. +metadata: + model: models/gemini-3.1-pro-preview + last_modified: Fri, 24 Apr 2026 15:09:34 GMT +--- +# Analyzing and Fixing Dart Code + +## Contents +- [Analysis Configuration](#analysis-configuration) +- [Diagnostic Suppression](#diagnostic-suppression) +- [Workflow: Executing Static Analysis](#workflow-executing-static-analysis) +- [Workflow: Applying Automated Fixes](#workflow-applying-automated-fixes) +- [Examples](#examples) + +## Analysis Configuration + +Configure the Dart analyzer using the `analysis_options.yaml` file located at the package root. + +- **Base Configuration:** Always include a standard rule set (e.g., `package:lints/recommended.yaml` or `package:flutter_lints/flutter.yaml`) using the `include:` directive. +- **Strict Type Checks:** Enable strict type checks under the `analyzer: language:` node to prevent implicit downcasts and dynamic inferences. Set `strict-casts: true`, `strict-inference: true`, and `strict-raw-types: true`. +- **Linter Rules:** Explicitly enable or disable specific rules under the `linter: rules:` node. Use a key-value map (`rule_name: true/false`) when overriding included rules, or a list (`- rule_name`) when defining a fresh set. Do not mix list and map syntax in the same `rules` block. +- **Formatter Configuration:** Configure `dart format` behavior under the `formatter:` node. Set `page_width` (default 80) and `trailing_commas` (`automate` or `preserve`). +- **Analyzer Plugins:** Enable custom diagnostics by adding plugins under the `analyzer: plugins:` node. Ensure the plugin package is added as a `dev_dependency` in `pubspec.yaml`. + +## Diagnostic Suppression + +When a diagnostic (lint or warning) yields a false positive or applies to generated code, suppress it explicitly. + +- **File-level Exclusion:** Use the `analyzer: exclude:` node in `analysis_options.yaml` to exclude entire files or directories (e.g., `**/*.g.dart`) using glob patterns. +- **File-level Suppression:** Add `// ignore_for_file: ` at the top of a Dart file to suppress specific diagnostics for the entire file. Use `// ignore_for_file: type=lint` to suppress all linter rules. +- **Line-level Suppression:** Add `// ignore: ` on the line directly above the offending code, or appended to the end of the offending line. +- **Pubspec Suppression:** Add `# ignore: ` above the offending line in `pubspec.yaml` files (e.g., `# ignore: sort_pub_dependencies`). +- **Plugin Diagnostics:** Prefix the diagnostic code with the plugin name when suppressing plugin-specific issues (e.g., `// ignore: some_plugin/some_code`). + +## Workflow: Executing Static Analysis + +Use this workflow to identify type-related bugs, style violations, and potential runtime errors. + +**Task Progress:** +- [ ] 1. Verify `analysis_options.yaml` exists at the project root. +- [ ] 2. Run the analyzer using the `analyze_files` MCP tool (if available) or the CLI command `dart analyze `. +- [ ] 3. Review the diagnostic output. +- [ ] 4. If info-level issues must be treated as failures, append the `--fatal-infos` flag. +- [ ] 5. Resolve reported errors manually or proceed to the Automated Fixes workflow. + +## Workflow: Applying Automated Fixes + +Use this workflow to resolve outdated API usages, apply quick fixes, and migrate code (e.g., Dart 3 migrations). + +**Task Progress:** +- [ ] 1. Execute a dry run to preview proposed changes using the `dart_fix` MCP tool or CLI command `dart fix --dry-run`. +- [ ] 2. Review the proposed fixes to ensure they align with the intended architecture. +- [ ] 3. If additional fixes are required, verify that the corresponding linter rules are enabled in `analysis_options.yaml`. +- [ ] 4. Apply the fixes using the `dart_fix` MCP tool or CLI command `dart fix --apply`. +- [ ] 5. Format the modified code using the `dart_format` MCP tool or CLI command `dart format .`. +- [ ] 6. Run the static analysis workflow to verify all diagnostics are resolved. + +## Examples + +### Comprehensive `analysis_options.yaml` + +```yaml +include: package:flutter_lints/recommended.yaml + +analyzer: + exclude: + - "**/*.g.dart" + - "lib/generated/**" + language: + strict-casts: true + strict-inference: true + strict-raw-types: true + errors: + todo: ignore + invalid_assignment: warning + missing_return: error + +linter: + rules: + avoid_shadowing_type_parameters: false + await_only_futures: true + use_super_parameters: true + +formatter: + page_width: 100 + trailing_commas: preserve +``` + +### Inline Diagnostic Suppression + +```dart +// Suppress for the entire file +// ignore_for_file: unused_local_variable, dead_code + +void processData() { + // Suppress for a specific line + // ignore: invalid_assignment + int x = ''; + + const y = 10; // ignore: constant_identifier_names +} +``` diff --git a/third_party/dart-lang-skills/LICENSE b/third_party/dart-lang-skills/LICENSE new file mode 100644 index 000000000000..dd5b36bbf37f --- /dev/null +++ b/third_party/dart-lang-skills/LICENSE @@ -0,0 +1,26 @@ +Copyright 2012, the Dart project authors. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/third_party/dart-lang-skills/skills-lock.json b/third_party/dart-lang-skills/skills-lock.json new file mode 100644 index 000000000000..69e38d2c2826 --- /dev/null +++ b/third_party/dart-lang-skills/skills-lock.json @@ -0,0 +1,29 @@ +{ + "version": 1, + "skills": { + "dart-add-unit-test": { + "source": "dart-lang/skills", + "sourceType": "github", + "skillPath": "skills/dart-add-unit-test/SKILL.md", + "computedHash": "326a2b6cb57bcb4f40203e063a1060e9549a8cea0ece1a5c9a0d39a2f2b85bc8" + }, + "dart-collect-coverage": { + "source": "dart-lang/skills", + "sourceType": "github", + "skillPath": "skills/dart-collect-coverage/SKILL.md", + "computedHash": "36d77c4ebc2edc7ade399ae8461776dd33a461899afda4b2b9d8e7f599d2ef6c" + }, + "dart-generate-test-mocks": { + "source": "dart-lang/skills", + "sourceType": "github", + "skillPath": "skills/dart-generate-test-mocks/SKILL.md", + "computedHash": "7bf12a98d63e96ed51ba077a24426be1b11a9fc508f7dfca60fc6e118c3fc923" + }, + "dart-run-static-analysis": { + "source": "dart-lang/skills", + "sourceType": "github", + "skillPath": "skills/dart-run-static-analysis/SKILL.md", + "computedHash": "e64ea092e216ecdc9b4a8b49b06d0b9a0b49f680c1116ada1d247b2de3fa6fe1" + } + } +} diff --git a/third_party/flutter-skills/.agents/skills/flutter-add-integration-test/SKILL.md b/third_party/flutter-skills/.agents/skills/flutter-add-integration-test/SKILL.md new file mode 100644 index 000000000000..60902f1aa315 --- /dev/null +++ b/third_party/flutter-skills/.agents/skills/flutter-add-integration-test/SKILL.md @@ -0,0 +1,163 @@ +--- +name: flutter-add-integration-test +description: Configures Flutter Driver for app interaction and converts MCP actions into permanent integration tests. Use when adding integration testing to a project, exploring UI components via MCP, or automating user flows with the integration_test package. +metadata: + model: models/gemini-3.1-pro-preview + last_modified: Tue, 21 Apr 2026 18:29:20 GMT +--- +# Implementing Flutter Integration Tests + +## Contents +- [Project Setup and Dependencies](#project-setup-and-dependencies) +- [Interactive Exploration via MCP](#interactive-exploration-via-mcp) +- [Test Authoring Guidelines](#test-authoring-guidelines) +- [Execution and Profiling](#execution-and-profiling) +- [Workflow: End-to-End Integration Testing](#workflow-end-to-end-integration-testing) +- [Examples](#examples) + +## Project Setup and Dependencies + +Configure the project to support integration testing and Flutter Driver extensions. + +1. Add required development dependencies to `pubspec.yaml`: + ```bash + flutter pub add 'dev:integration_test:{"sdk":"flutter"}' + flutter pub add 'dev:flutter_test:{"sdk":"flutter"}' + ``` +2. Enable the Flutter Driver extension in your application entry point (typically `lib/main.dart` or a dedicated `lib/main_test.dart`): + - Import `package:flutter_driver/driver_extension.dart`. + - Call `enableFlutterDriverExtension();` before `runApp()`. +3. Add `Key` parameters (e.g., `ValueKey('login_button')`) to critical widgets in the application code to ensure reliable targeting during tests. + +## Interactive Exploration via MCP + +Use the Dart/Flutter MCP server tools to interactively explore and manipulate the application state before writing static tests. + +- **Launch**: Execute `launch_app` with `target: "lib/main_test.dart"` to start the application and acquire the DTD URI. +- **Inspect**: Execute `get_widget_tree` to discover available `Key`s, `Text` nodes, and widget `Type`s. +- **Interact**: Execute `tap`, `enter_text`, and `scroll` to simulate user flows. +- **Wait**: Always execute `waitFor` or verify state with `get_health` when navigating or triggering animations. +- **Troubleshoot Unmounted Widgets**: If a widget is not found in the tree, it may be lazily loaded in a `SliverList` or `ListView`. Execute `scroll` or `scrollIntoView` to force the widget to mount before interacting with it. + +## Test Authoring Guidelines + +Structure integration tests using the `flutter_test` API paradigm. + +- Create a dedicated `integration_test/` directory at the project root. +- Name all test files using the `_test.dart` convention. +- Initialize the binding by calling `IntegrationTestWidgetsFlutterBinding.ensureInitialized();` at the start of `main()`. +- Load the application UI using `await tester.pumpWidget(MyApp());`. +- Trigger frames and wait for animations to complete using `await tester.pumpAndSettle();` after interactions like `tester.tap()`. +- Assert widget visibility using `expect(find.byKey(ValueKey('foo')), findsOneWidget);` or `findsNothing`. +- Scroll to specific off-screen widgets using `await tester.scrollUntilVisible(itemFinder, 500.0, scrollable: listFinder);`. + +**Conditional Logic for Legacy `flutter_driver`:** +- If maintaining or migrating legacy `flutter_driver` tests, use `driver.waitFor()`, `driver.waitForAbsent()`, `driver.tap()`, and `driver.scroll()` instead of the `WidgetTester` APIs. + +## Execution and Profiling + +Execute tests using the `flutter drive` command. Require a host driver script located in `test_driver/integration_test.dart` that calls `integrationDriver()`. + +**Conditional Execution Targets:** +- **If testing on Chrome:** Launch `chromedriver --port=4444` in a separate terminal, then run: + `flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart -d chrome` +- **If testing headless web:** Run with `-d web-server`. +- **If testing on Android (Local):** Run `flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart`. +- **If testing on Firebase Test Lab (Android):** + 1. Build debug APK: `flutter build apk --debug` + 2. Build test APK: `./gradlew app:assembleAndroidTest` + 3. Upload both APKs to the Firebase Test Lab console. + +## Workflow: End-to-End Integration Testing + +Copy and follow this checklist to implement and verify integration tests. + +- [ ] **Task Progress: Setup** + - [ ] Add `integration_test` and `flutter_test` to `pubspec.yaml`. + - [ ] Inject `enableFlutterDriverExtension()` into the app entry point. + - [ ] Assign `ValueKey`s to target widgets. +- [ ] **Task Progress: Exploration** + - [ ] Run `launch_app` via MCP. + - [ ] Map the widget tree using `get_widget_tree`. + - [ ] Validate interaction paths using MCP tools (`tap`, `enter_text`). +- [ ] **Task Progress: Authoring** + - [ ] Create `integration_test/app_test.dart`. + - [ ] Write test cases using `WidgetTester` APIs. + - [ ] Create `test_driver/integration_test.dart` with `integrationDriver()`. +- [ ] **Task Progress: Execution & Feedback Loop** + - [ ] Run `flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart`. + - [ ] **Feedback Loop**: Review test output -> If `PumpAndSettleTimedOutException` occurs, check for infinite animations -> If widget not found, add `scrollUntilVisible` -> Re-run test until passing. + +## Examples + +### Standard Integration Test (`integration_test/app_test.dart`) + +```dart +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:my_app/main.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('End-to-end test', () { + testWidgets('tap on the floating action button, verify counter', (tester) async { + // Load app widget. + await tester.pumpWidget(const MyApp()); + + // Verify the counter starts at 0. + expect(find.text('0'), findsOneWidget); + + // Find the floating action button to tap on. + final fab = find.byKey(const ValueKey('increment')); + + // Emulate a tap on the floating action button. + await tester.tap(fab); + + // Trigger a frame and wait for animations. + await tester.pumpAndSettle(); + + // Verify the counter increments by 1. + expect(find.text('1'), findsOneWidget); + }); + }); +} +``` + +### Host Driver Script (`test_driver/integration_test.dart`) + +```dart +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); +``` + +### Performance Profiling Driver Script (`test_driver/perf_driver.dart`) + +Use this driver script if you wrap your test actions in `binding.traceAction()` to capture performance metrics. + +```dart +import 'package:flutter_driver/flutter_driver.dart' as driver; +import 'package:integration_test/integration_test_driver.dart'; + +Future main() { + return integrationDriver( + responseDataCallback: (data) async { + if (data != null) { + final timeline = driver.Timeline.fromJson( + data['scrolling_timeline'] as Map, + ); + + final summary = driver.TimelineSummary.summarize(timeline); + + await summary.writeTimelineToFile( + 'scrolling_timeline', + pretty: true, + includeSummary: true, + ); + } + }, + ); +} +``` diff --git a/third_party/flutter-skills/LICENSE b/third_party/flutter-skills/LICENSE new file mode 100644 index 000000000000..fb3be4170486 --- /dev/null +++ b/third_party/flutter-skills/LICENSE @@ -0,0 +1,26 @@ +Copyright 2026 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/third_party/flutter-skills/skills-lock.json b/third_party/flutter-skills/skills-lock.json new file mode 100644 index 000000000000..bb535ddfaf96 --- /dev/null +++ b/third_party/flutter-skills/skills-lock.json @@ -0,0 +1,11 @@ +{ + "version": 1, + "skills": { + "flutter-add-integration-test": { + "source": "flutter/skills", + "sourceType": "github", + "skillPath": "skills/flutter-add-integration-test/SKILL.md", + "computedHash": "9ead37fef54371fed6ad07a9ba3de7a908135a0867b05baf74c28b7095343999" + } + } +} diff --git a/third_party/mattpocock-skills/.agents/skills/grill-me/SKILL.md b/third_party/mattpocock-skills/.agents/skills/grill-me/SKILL.md new file mode 100644 index 000000000000..9470cfcfe231 --- /dev/null +++ b/third_party/mattpocock-skills/.agents/skills/grill-me/SKILL.md @@ -0,0 +1,7 @@ +--- +name: grill-me +description: A relentless interview to sharpen a plan or design. +disable-model-invocation: true +--- + +Run a `/grilling` session. diff --git a/third_party/mattpocock-skills/LICENSE b/third_party/mattpocock-skills/LICENSE new file mode 100644 index 000000000000..f1dd2c09108d --- /dev/null +++ b/third_party/mattpocock-skills/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Matt Pocock + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/mattpocock-skills/skills-lock.json b/third_party/mattpocock-skills/skills-lock.json new file mode 100644 index 000000000000..4a5bc62c2420 --- /dev/null +++ b/third_party/mattpocock-skills/skills-lock.json @@ -0,0 +1,11 @@ +{ + "version": 1, + "skills": { + "grill-me": { + "source": "mattpocock/skills", + "sourceType": "github", + "skillPath": "skills/productivity/grill-me/SKILL.md", + "computedHash": "f321507f77702a54af1db66549ec1685fc625390d06c57d7949cdcda8eb1b5c7" + } + } +} diff --git a/third_party/obra-superpowers/.agents/skills/receiving-code-review/SKILL.md b/third_party/obra-superpowers/.agents/skills/receiving-code-review/SKILL.md new file mode 100644 index 000000000000..4c77a10ee338 --- /dev/null +++ b/third_party/obra-superpowers/.agents/skills/receiving-code-review/SKILL.md @@ -0,0 +1,213 @@ +--- +name: receiving-code-review +description: Use when receiving code review feedback, before implementing suggestions, especially if feedback seems unclear or technically questionable - requires technical rigor and verification, not performative agreement or blind implementation +--- + +# Code Review Reception + +## Overview + +Code review requires technical evaluation, not emotional performance. + +**Core principle:** Verify before implementing. Ask before assuming. Technical correctness over social comfort. + +## The Response Pattern + +``` +WHEN receiving code review feedback: + +1. READ: Complete feedback without reacting +2. UNDERSTAND: Restate requirement in own words (or ask) +3. VERIFY: Check against codebase reality +4. EVALUATE: Technically sound for THIS codebase? +5. RESPOND: Technical acknowledgment or reasoned pushback +6. IMPLEMENT: One item at a time, test each +``` + +## Forbidden Responses + +**NEVER:** +- "You're absolutely right!" (explicit instruction-file violation) +- "Great point!" / "Excellent feedback!" (performative) +- "Let me implement that now" (before verification) + +**INSTEAD:** +- Restate the technical requirement +- Ask clarifying questions +- Push back with technical reasoning if wrong +- Just start working (actions > words) + +## Handling Unclear Feedback + +``` +IF any item is unclear: + STOP - do not implement anything yet + ASK for clarification on unclear items + +WHY: Items may be related. Partial understanding = wrong implementation. +``` + +**Example:** +``` +your human partner: "Fix 1-6" +You understand 1,2,3,6. Unclear on 4,5. + +❌ WRONG: Implement 1,2,3,6 now, ask about 4,5 later +✅ RIGHT: "I understand items 1,2,3,6. Need clarification on 4 and 5 before proceeding." +``` + +## Source-Specific Handling + +### From your human partner +- **Trusted** - implement after understanding +- **Still ask** if scope unclear +- **No performative agreement** +- **Skip to action** or technical acknowledgment + +### From External Reviewers +``` +BEFORE implementing: + 1. Check: Technically correct for THIS codebase? + 2. Check: Breaks existing functionality? + 3. Check: Reason for current implementation? + 4. Check: Works on all platforms/versions? + 5. Check: Does reviewer understand full context? + +IF suggestion seems wrong: + Push back with technical reasoning + +IF can't easily verify: + Say so: "I can't verify this without [X]. Should I [investigate/ask/proceed]?" + +IF conflicts with your human partner's prior decisions: + Stop and discuss with your human partner first +``` + +**your human partner's rule:** "External feedback - be skeptical, but check carefully" + +## YAGNI Check for "Professional" Features + +``` +IF reviewer suggests "implementing properly": + grep codebase for actual usage + + IF unused: "This endpoint isn't called. Remove it (YAGNI)?" + IF used: Then implement properly +``` + +**your human partner's rule:** "You and reviewer both report to me. If we don't need this feature, don't add it." + +## Implementation Order + +``` +FOR multi-item feedback: + 1. Clarify anything unclear FIRST + 2. Then implement in this order: + - Blocking issues (breaks, security) + - Simple fixes (typos, imports) + - Complex fixes (refactoring, logic) + 3. Test each fix individually + 4. Verify no regressions +``` + +## When To Push Back + +Push back when: +- Suggestion breaks existing functionality +- Reviewer lacks full context +- Violates YAGNI (unused feature) +- Technically incorrect for this stack +- Legacy/compatibility reasons exist +- Conflicts with your human partner's architectural decisions + +**How to push back:** +- Use technical reasoning, not defensiveness +- Ask specific questions +- Reference working tests/code +- Involve your human partner if architectural + +**If you're uncomfortable pushing back out loud:** Name that tension, then tell your partner about the issue you've seen. They'll appreciate your honesty. + +## Acknowledging Correct Feedback + +When feedback IS correct: +``` +✅ "Fixed. [Brief description of what changed]" +✅ "Good catch - [specific issue]. Fixed in [location]." +✅ [Just fix it and show in the code] + +❌ "You're absolutely right!" +❌ "Great point!" +❌ "Thanks for catching that!" +❌ "Thanks for [anything]" +❌ ANY gratitude expression +``` + +**Why no thanks:** Actions speak. Just fix it. The code itself shows you heard the feedback. + +**If you catch yourself about to write "Thanks":** DELETE IT. State the fix instead. + +## Gracefully Correcting Your Pushback + +If you pushed back and were wrong: +``` +✅ "You were right - I checked [X] and it does [Y]. Implementing now." +✅ "Verified this and you're correct. My initial understanding was wrong because [reason]. Fixing." + +❌ Long apology +❌ Defending why you pushed back +❌ Over-explaining +``` + +State the correction factually and move on. + +## Common Mistakes + +| Mistake | Fix | +|---------|-----| +| Performative agreement | State requirement or just act | +| Blind implementation | Verify against codebase first | +| Batch without testing | One at a time, test each | +| Assuming reviewer is right | Check if breaks things | +| Avoiding pushback | Technical correctness > comfort | +| Partial implementation | Clarify all items first | +| Can't verify, proceed anyway | State limitation, ask for direction | + +## Real Examples + +**Performative Agreement (Bad):** +``` +Reviewer: "Remove legacy code" +❌ "You're absolutely right! Let me remove that..." +``` + +**Technical Verification (Good):** +``` +Reviewer: "Remove legacy code" +✅ "Checking... build target is 10.15+, this API needs 13+. Need legacy for backward compat. Current impl has wrong bundle ID - fix it or drop pre-13 support?" +``` + +**YAGNI (Good):** +``` +Reviewer: "Implement proper metrics tracking with database, date filters, CSV export" +✅ "Grepped codebase - nothing calls this endpoint. Remove it (YAGNI)? Or is there usage I'm missing?" +``` + +**Unclear Item (Good):** +``` +your human partner: "Fix items 1-6" +You understand 1,2,3,6. Unclear on 4,5. +✅ "Understand 1,2,3,6. Need clarification on 4 and 5 before implementing." +``` + +## GitHub Thread Replies + +When replying to inline review comments on GitHub, reply in the comment thread (`gh api repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies`), not as a top-level PR comment. + +## The Bottom Line + +**External feedback = suggestions to evaluate, not orders to follow.** + +Verify. Question. Then implement. + +No performative agreement. Technical rigor always. diff --git a/third_party/obra-superpowers/LICENSE b/third_party/obra-superpowers/LICENSE new file mode 100644 index 000000000000..abf0390320aa --- /dev/null +++ b/third_party/obra-superpowers/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Jesse Vincent + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/obra-superpowers/skills-lock.json b/third_party/obra-superpowers/skills-lock.json new file mode 100644 index 000000000000..9e3054a14098 --- /dev/null +++ b/third_party/obra-superpowers/skills-lock.json @@ -0,0 +1,11 @@ +{ + "version": 1, + "skills": { + "receiving-code-review": { + "source": "obra/superpowers", + "sourceType": "github", + "skillPath": "skills/receiving-code-review/SKILL.md", + "computedHash": "3f56080356c62e4f74a183d7371686babc661e41a952dc67a97c2c76fe8a9329" + } + } +} From 26b37ae84d6e6aad891589c5f9b2cab93f5d333c Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Wed, 17 Jun 2026 16:55:37 -0400 Subject: [PATCH 02/25] feat: Add check-readiness skill --- .../.agents/skills/.gitignore | 1 + .../.agents/skills/check-readiness/SKILL.md | 17 ++++++ .../skills/check-readiness/scripts/check.sh | 52 +++++++++++++++++++ .../dart_skills_lint.yaml | 4 +- 4 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 packages/camera/camera_android_camerax/.agents/skills/check-readiness/SKILL.md create mode 100755 packages/camera/camera_android_camerax/.agents/skills/check-readiness/scripts/check.sh diff --git a/packages/camera/camera_android_camerax/.agents/skills/.gitignore b/packages/camera/camera_android_camerax/.agents/skills/.gitignore index bf61c92cbf22..f70d8ee99781 100644 --- a/packages/camera/camera_android_camerax/.agents/skills/.gitignore +++ b/packages/camera/camera_android_camerax/.agents/skills/.gitignore @@ -3,6 +3,7 @@ # Un-ignore specific checked-in skills # (Add specific contributor skills here as they are created) +!check-readiness/ # Keep essential configuration and docs !.gitignore diff --git a/packages/camera/camera_android_camerax/.agents/skills/check-readiness/SKILL.md b/packages/camera/camera_android_camerax/.agents/skills/check-readiness/SKILL.md new file mode 100644 index 000000000000..581c2943b602 --- /dev/null +++ b/packages/camera/camera_android_camerax/.agents/skills/check-readiness/SKILL.md @@ -0,0 +1,17 @@ +--- +name: check-readiness +description: Run this skill to check if the repository is ready for new work. Use this skill whenever the user asks to "check readiness", "see if we are ready to start work", or when starting a new task in the camera_android_camerax package. +--- +# Check Readiness + +This skill verifies that the local environment is properly configured and clean before starting new work in the `camera_android_camerax` package. + +## Instructions +Run the bundled verification script to perform the automated environment checks: +```bash +bash .agents/skills/check-readiness/scripts/check.sh +``` + +### Handling the Results +1. **If the script succeeds:** Inform the user that the environment is clean, dependencies are resolved, and it is ready for new work. +2. **If the script fails:** Explain exactly which check failed (e.g., git is not clean, a symlink is broken, Flutter is missing from PATH) and offer to help resolve it. diff --git a/packages/camera/camera_android_camerax/.agents/skills/check-readiness/scripts/check.sh b/packages/camera/camera_android_camerax/.agents/skills/check-readiness/scripts/check.sh new file mode 100755 index 000000000000..d6a2b09e1eb1 --- /dev/null +++ b/packages/camera/camera_android_camerax/.agents/skills/check-readiness/scripts/check.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# Stop on first error +set -e + +# Get the directory of this script, then go up to camera_android_camerax root +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +CAMERAX_DIR="$SCRIPT_DIR/../../../.." + +echo "🔍 Checking if environment is ready for new work..." + +# 1. Check symlinks resolve +echo "1️⃣ Checking skill symlinks..." +broken_links=$(find "$CAMERAX_DIR/.agents/skills" -type l ! -exec test -e {} \; -print) +if [ -n "$broken_links" ]; then + echo "❌ Error: Found broken symlinks in .agents/skills:" + echo "$broken_links" + exit 1 +fi +echo "✅ All symlinks resolve correctly." + +# 2. Check git state +echo "2️⃣ Checking git state..." +# Check the whole repository git state +if [ -n "$(git status --porcelain)" ]; then + echo "❌ Error: Git working directory is not clean. Please commit or stash your changes before starting new work." + exit 1 +fi +echo "✅ Git working directory is clean." + +# 3. Check dart and flutter +echo "3️⃣ Checking Flutter and Dart..." +if ! command -v flutter &> /dev/null; then + echo "❌ Error: 'flutter' is not on the PATH." + exit 1 +fi +if ! command -v dart &> /dev/null; then + echo "❌ Error: 'dart' is not on the PATH." + exit 1 +fi +echo "✅ Flutter and Dart are on the PATH." + +# 4. Check dependencies in camera_android_camerax +echo "4️⃣ Checking dependencies in camera_android_camerax..." +cd "$CAMERAX_DIR" +if ! flutter pub get; then + echo "❌ Error: Failed to resolve dependencies." + exit 1 +fi +echo "✅ Dependencies are resolved and ready." + +echo "🎉 Environment is fully ready!" diff --git a/packages/camera/camera_android_camerax/dart_skills_lint.yaml b/packages/camera/camera_android_camerax/dart_skills_lint.yaml index d7ecc80bc610..90bde3cd1e0a 100644 --- a/packages/camera/camera_android_camerax/dart_skills_lint.yaml +++ b/packages/camera/camera_android_camerax/dart_skills_lint.yaml @@ -4,6 +4,4 @@ dart_skills_lint: check-trailing-whitespace: error directories: - path: "skills" - - path: ".agents/skills" - rules: - prevent-skills-sh-publishing: error + - path: ".agents/skills/check-readiness" From d825767d7fab9824d24da71028ebaea2de25cb67 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Wed, 17 Jun 2026 17:00:40 -0400 Subject: [PATCH 03/25] feat: Add local check-readiness skill and configure linter --- .../.agents/skills/check-readiness/SKILL.md | 2 ++ packages/camera/camera_android_camerax/dart_skills_lint.yaml | 3 +++ .../camera_android_camerax/test/validate_skills_test.dart | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera_android_camerax/.agents/skills/check-readiness/SKILL.md b/packages/camera/camera_android_camerax/.agents/skills/check-readiness/SKILL.md index 581c2943b602..54b33d915b15 100644 --- a/packages/camera/camera_android_camerax/.agents/skills/check-readiness/SKILL.md +++ b/packages/camera/camera_android_camerax/.agents/skills/check-readiness/SKILL.md @@ -1,6 +1,8 @@ --- name: check-readiness description: Run this skill to check if the repository is ready for new work. Use this skill whenever the user asks to "check readiness", "see if we are ready to start work", or when starting a new task in the camera_android_camerax package. +metadata: + internal: true --- # Check Readiness diff --git a/packages/camera/camera_android_camerax/dart_skills_lint.yaml b/packages/camera/camera_android_camerax/dart_skills_lint.yaml index 90bde3cd1e0a..1148e2fc84dd 100644 --- a/packages/camera/camera_android_camerax/dart_skills_lint.yaml +++ b/packages/camera/camera_android_camerax/dart_skills_lint.yaml @@ -4,4 +4,7 @@ dart_skills_lint: check-trailing-whitespace: error directories: - path: "skills" + individual_skills: - path: ".agents/skills/check-readiness" + rules: + prevent-skills-sh-publishing: error diff --git a/packages/camera/camera_android_camerax/test/validate_skills_test.dart b/packages/camera/camera_android_camerax/test/validate_skills_test.dart index e7bd4e19c292..3204d4c67925 100644 --- a/packages/camera/camera_android_camerax/test/validate_skills_test.dart +++ b/packages/camera/camera_android_camerax/test/validate_skills_test.dart @@ -6,7 +6,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:logging/logging.dart'; void main() { - test('Validate Agent Skills', () async { + test('Validate Skills', () async { final Level oldLevel = Logger.root.level; Logger.root.level = Level.ALL; final StreamSubscription subscription = Logger.root.onRecord.listen((record) { From f7f0646f81a64ac5e0171af42a1998b387bf2f9f Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Wed, 17 Jun 2026 17:12:02 -0400 Subject: [PATCH 04/25] test: ensure tracked skills cannot be published accidentally --- .../test/tracked_skills_publishing_test.dart | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart diff --git a/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart b/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart new file mode 100644 index 000000000000..89a4c6c29870 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart @@ -0,0 +1,78 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:yaml/yaml.dart'; + +void main() { + test('all tracked skills have prevent-skills-sh-publishing rule explicitly configured', () async { + // Explanation: + // Any skill in .agents/skills/ that is checked into version control is considered an internal skill. + // It must explicitly have the `prevent-skills-sh-publishing` rule configured in dart_skills_lint.yaml + // to prevent accidental publishing. + + // 1. Get tracked files using git ls-files + final ProcessResult processResult = await Process.run('git', ['ls-files', '.agents/skills']); + expect(processResult.exitCode, 0, reason: 'git ls-files should succeed'); + + final output = processResult.stdout as String; + final Iterable lines = output.split('\n').where((line) => line.trim().isNotEmpty); + + final trackedSkillDirs = {}; + for (final line in lines) { + final List parts = line.split('/'); + // We look for files inside .agents/skills// + // parts[0] is .agents, parts[1] is skills + if (parts.length >= 4 && parts[0] == '.agents' && parts[1] == 'skills') { + trackedSkillDirs.add(parts[2]); + } + } + + expect(trackedSkillDirs, isNotEmpty, reason: 'Should find at least one tracked skill'); + + // 2. Parse configuration + final File yamlFile = File('dart_skills_lint.yaml'); + final YamlMap yamlConfig = loadYaml(yamlFile.readAsStringSync()); + final YamlMap? toolConfig = yamlConfig['dart_skills_lint'] as YamlMap?; + expect(toolConfig, isNotNull, reason: 'dart_skills_lint config missing'); + + for (final skillDir in trackedSkillDirs) { + final expectedPath = '.agents/skills/$skillDir'; + bool hasRule = false; + + // Check directories + final dirs = toolConfig!['directories'] as YamlList?; + if (dirs != null) { + for (final dir in dirs) { + if (dir is YamlMap && (dir['path'] == expectedPath || dir['path'] == '.agents/skills')) { + if (dir['rules'] is YamlMap && dir['rules']['prevent-skills-sh-publishing'] != null) { + hasRule = true; + } + } + } + } + + // Check hypothetical individual_skills + final individualSkills = toolConfig['individual_skills'] as YamlList?; + if (individualSkills != null) { + for (final skill in individualSkills) { + if (skill is YamlMap && skill['path'] == expectedPath) { + if (skill['rules'] is YamlMap && skill['rules']['prevent-skills-sh-publishing'] != null) { + hasRule = true; + } + } + } + } + + expect( + hasRule, + isTrue, + reason: + 'The tracked skill "$skillDir" must have "prevent-skills-sh-publishing" explicitly configured in dart_skills_lint.yaml.', + ); + } + }); +} From ea27e434033041dc9054159a2351b714e89500da Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Wed, 17 Jun 2026 17:53:08 -0400 Subject: [PATCH 05/25] build: pin dart_skills_lint to support individual_skills --- packages/camera/camera_android_camerax/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 61eaf7a37522..66d14f153f97 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -31,6 +31,7 @@ dev_dependencies: git: url: https://github.com/flutter/skills.git path: tool/dart_skills_lint + ref: 35433e97b322b15433d8efceed8674ea9c9ebd0c flutter_test: sdk: flutter leak_tracker_flutter_testing: any From 9ac31a2e61832f646f26df46c57540dfb12acec4 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Wed, 17 Jun 2026 17:55:04 -0400 Subject: [PATCH 06/25] test: rewrite tracked skills test to use ValidationSession API --- .../skills/check-readiness/scripts/check.sh | 52 ------------------ .../test/tracked_skills_publishing_test.dart | 55 +++++++------------ .../flutter-add-integration-test/SKILL.md | 4 +- 3 files changed, 23 insertions(+), 88 deletions(-) delete mode 100755 packages/camera/camera_android_camerax/.agents/skills/check-readiness/scripts/check.sh diff --git a/packages/camera/camera_android_camerax/.agents/skills/check-readiness/scripts/check.sh b/packages/camera/camera_android_camerax/.agents/skills/check-readiness/scripts/check.sh deleted file mode 100755 index d6a2b09e1eb1..000000000000 --- a/packages/camera/camera_android_camerax/.agents/skills/check-readiness/scripts/check.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash - -# Stop on first error -set -e - -# Get the directory of this script, then go up to camera_android_camerax root -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -CAMERAX_DIR="$SCRIPT_DIR/../../../.." - -echo "🔍 Checking if environment is ready for new work..." - -# 1. Check symlinks resolve -echo "1️⃣ Checking skill symlinks..." -broken_links=$(find "$CAMERAX_DIR/.agents/skills" -type l ! -exec test -e {} \; -print) -if [ -n "$broken_links" ]; then - echo "❌ Error: Found broken symlinks in .agents/skills:" - echo "$broken_links" - exit 1 -fi -echo "✅ All symlinks resolve correctly." - -# 2. Check git state -echo "2️⃣ Checking git state..." -# Check the whole repository git state -if [ -n "$(git status --porcelain)" ]; then - echo "❌ Error: Git working directory is not clean. Please commit or stash your changes before starting new work." - exit 1 -fi -echo "✅ Git working directory is clean." - -# 3. Check dart and flutter -echo "3️⃣ Checking Flutter and Dart..." -if ! command -v flutter &> /dev/null; then - echo "❌ Error: 'flutter' is not on the PATH." - exit 1 -fi -if ! command -v dart &> /dev/null; then - echo "❌ Error: 'dart' is not on the PATH." - exit 1 -fi -echo "✅ Flutter and Dart are on the PATH." - -# 4. Check dependencies in camera_android_camerax -echo "4️⃣ Checking dependencies in camera_android_camerax..." -cd "$CAMERAX_DIR" -if ! flutter pub get; then - echo "❌ Error: Failed to resolve dependencies." - exit 1 -fi -echo "✅ Dependencies are resolved and ready." - -echo "🎉 Environment is fully ready!" diff --git a/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart b/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart index 89a4c6c29870..1b2423a1c3a3 100644 --- a/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart +++ b/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart @@ -4,8 +4,10 @@ import 'dart:io'; +import 'package:dart_skills_lint/src/config_parser.dart'; +import 'package:dart_skills_lint/src/models/analysis_severity.dart'; +import 'package:dart_skills_lint/src/validation_session.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:yaml/yaml.dart'; void main() { test('all tracked skills have prevent-skills-sh-publishing rule explicitly configured', () async { @@ -15,15 +17,15 @@ void main() { // to prevent accidental publishing. // 1. Get tracked files using git ls-files - final ProcessResult processResult = await Process.run('git', ['ls-files', '.agents/skills']); + final processResult = await Process.run('git', ['ls-files', '.agents/skills']); expect(processResult.exitCode, 0, reason: 'git ls-files should succeed'); final output = processResult.stdout as String; - final Iterable lines = output.split('\n').where((line) => line.trim().isNotEmpty); + final lines = output.split('\n').where((line) => line.trim().isNotEmpty); final trackedSkillDirs = {}; for (final line in lines) { - final List parts = line.split('/'); + final parts = line.split('/'); // We look for files inside .agents/skills// // parts[0] is .agents, parts[1] is skills if (parts.length >= 4 && parts[0] == '.agents' && parts[1] == 'skills') { @@ -34,41 +36,26 @@ void main() { expect(trackedSkillDirs, isNotEmpty, reason: 'Should find at least one tracked skill'); // 2. Parse configuration - final File yamlFile = File('dart_skills_lint.yaml'); - final YamlMap yamlConfig = loadYaml(yamlFile.readAsStringSync()); - final YamlMap? toolConfig = yamlConfig['dart_skills_lint'] as YamlMap?; - expect(toolConfig, isNotNull, reason: 'dart_skills_lint config missing'); + final config = await ConfigParser.loadConfig(); + final session = ValidationSession( + config: config, + resolvedRules: {}, + ignoreFileOverride: null, + customRules: const [], + printWarnings: false, + fastFail: false, + quiet: true, + generateBaseline: false, + fix: false, + fixApply: false, + ); for (final skillDir in trackedSkillDirs) { final expectedPath = '.agents/skills/$skillDir'; - bool hasRule = false; - - // Check directories - final dirs = toolConfig!['directories'] as YamlList?; - if (dirs != null) { - for (final dir in dirs) { - if (dir is YamlMap && (dir['path'] == expectedPath || dir['path'] == '.agents/skills')) { - if (dir['rules'] is YamlMap && dir['rules']['prevent-skills-sh-publishing'] != null) { - hasRule = true; - } - } - } - } - - // Check hypothetical individual_skills - final individualSkills = toolConfig['individual_skills'] as YamlList?; - if (individualSkills != null) { - for (final skill in individualSkills) { - if (skill is YamlMap && skill['path'] == expectedPath) { - if (skill['rules'] is YamlMap && skill['rules']['prevent-skills-sh-publishing'] != null) { - hasRule = true; - } - } - } - } + final resolvedRules = session.resolveRulesForPath(expectedPath); expect( - hasRule, + resolvedRules.containsKey('prevent-skills-sh-publishing'), isTrue, reason: 'The tracked skill "$skillDir" must have "prevent-skills-sh-publishing" explicitly configured in dart_skills_lint.yaml.', diff --git a/third_party/flutter-skills/.agents/skills/flutter-add-integration-test/SKILL.md b/third_party/flutter-skills/.agents/skills/flutter-add-integration-test/SKILL.md index 60902f1aa315..9fb501ade188 100644 --- a/third_party/flutter-skills/.agents/skills/flutter-add-integration-test/SKILL.md +++ b/third_party/flutter-skills/.agents/skills/flutter-add-integration-test/SKILL.md @@ -41,7 +41,7 @@ Use the Dart/Flutter MCP server tools to interactively explore and manipulate th ## Test Authoring Guidelines -Structure integration tests using the `flutter_test` API paradigm. +Structure integration tests using the `flutter_test` API paradigm. - Create a dedicated `integration_test/` directory at the project root. - Name all test files using the `_test.dart` convention. @@ -63,7 +63,7 @@ Execute tests using the `flutter drive` command. Require a host driver script lo `flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart -d chrome` - **If testing headless web:** Run with `-d web-server`. - **If testing on Android (Local):** Run `flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart`. -- **If testing on Firebase Test Lab (Android):** +- **If testing on Firebase Test Lab (Android):** 1. Build debug APK: `flutter build apk --debug` 2. Build test APK: `./gradlew app:assembleAndroidTest` 3. Upload both APKs to the Firebase Test Lab console. From 87cfaf123d5ccf0db731267e0a7cfdde94736226 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Wed, 17 Jun 2026 17:55:31 -0400 Subject: [PATCH 07/25] test: fix analyzer warnings --- .../test/tracked_skills_publishing_test.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart b/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart index 1b2423a1c3a3..19a72cb437e2 100644 --- a/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart +++ b/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart @@ -17,15 +17,15 @@ void main() { // to prevent accidental publishing. // 1. Get tracked files using git ls-files - final processResult = await Process.run('git', ['ls-files', '.agents/skills']); + final ProcessResult processResult = await Process.run('git', ['ls-files', '.agents/skills']); expect(processResult.exitCode, 0, reason: 'git ls-files should succeed'); final output = processResult.stdout as String; - final lines = output.split('\n').where((line) => line.trim().isNotEmpty); + final Iterable lines = output.split('\n').where((line) => line.trim().isNotEmpty); final trackedSkillDirs = {}; for (final line in lines) { - final parts = line.split('/'); + final List parts = line.split('/'); // We look for files inside .agents/skills// // parts[0] is .agents, parts[1] is skills if (parts.length >= 4 && parts[0] == '.agents' && parts[1] == 'skills') { @@ -36,7 +36,7 @@ void main() { expect(trackedSkillDirs, isNotEmpty, reason: 'Should find at least one tracked skill'); // 2. Parse configuration - final config = await ConfigParser.loadConfig(); + final Configuration config = await ConfigParser.loadConfig(); final session = ValidationSession( config: config, resolvedRules: {}, @@ -52,7 +52,7 @@ void main() { for (final skillDir in trackedSkillDirs) { final expectedPath = '.agents/skills/$skillDir'; - final resolvedRules = session.resolveRulesForPath(expectedPath); + final Map resolvedRules = session.resolveRulesForPath(expectedPath); expect( resolvedRules.containsKey('prevent-skills-sh-publishing'), From acbcc3e606dc164a383414e28c2e19d39364a395 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Thu, 18 Jun 2026 11:33:19 -0400 Subject: [PATCH 08/25] build: update dart_skills_lint to 8f85e82b on main --- packages/camera/camera_android_camerax/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 66d14f153f97..34f4a7445e22 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -31,7 +31,7 @@ dev_dependencies: git: url: https://github.com/flutter/skills.git path: tool/dart_skills_lint - ref: 35433e97b322b15433d8efceed8674ea9c9ebd0c + ref: 8f85e82be6da429980f7cfe84f2f214a06cbfee1 flutter_test: sdk: flutter leak_tracker_flutter_testing: any From 7eabf5ff8e03431ba0c509a71791748195c39ac8 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Thu, 18 Jun 2026 18:46:06 -0400 Subject: [PATCH 09/25] test: rewrite tracked skills test to use yaml parsing, address PR comments --- .../.agents/skills/check-readiness/SKILL.md | 2 +- .../skills/check-readiness/scripts/check.sh | 52 +++++++++++++ .../camera_android_camerax/pubspec.yaml | 1 + .../test/tracked_skills_publishing_test.dart | 74 ++++++++++++------- third_party/README.md | 2 +- third_party/anthropics-skills/LICENSE | 1 - .../anthropics-skills/skills-lock.json | 11 --- 7 files changed, 104 insertions(+), 39 deletions(-) create mode 100755 packages/camera/camera_android_camerax/.agents/skills/check-readiness/scripts/check.sh delete mode 100644 third_party/anthropics-skills/LICENSE delete mode 100644 third_party/anthropics-skills/skills-lock.json diff --git a/packages/camera/camera_android_camerax/.agents/skills/check-readiness/SKILL.md b/packages/camera/camera_android_camerax/.agents/skills/check-readiness/SKILL.md index 54b33d915b15..fb14888cf2e6 100644 --- a/packages/camera/camera_android_camerax/.agents/skills/check-readiness/SKILL.md +++ b/packages/camera/camera_android_camerax/.agents/skills/check-readiness/SKILL.md @@ -9,7 +9,7 @@ metadata: This skill verifies that the local environment is properly configured and clean before starting new work in the `camera_android_camerax` package. ## Instructions -Run the bundled verification script to perform the automated environment checks: +Run the bundled verification script ([scripts/check.sh](scripts/check.sh)) to perform the automated environment checks: ```bash bash .agents/skills/check-readiness/scripts/check.sh ``` diff --git a/packages/camera/camera_android_camerax/.agents/skills/check-readiness/scripts/check.sh b/packages/camera/camera_android_camerax/.agents/skills/check-readiness/scripts/check.sh new file mode 100755 index 000000000000..d6a2b09e1eb1 --- /dev/null +++ b/packages/camera/camera_android_camerax/.agents/skills/check-readiness/scripts/check.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# Stop on first error +set -e + +# Get the directory of this script, then go up to camera_android_camerax root +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +CAMERAX_DIR="$SCRIPT_DIR/../../../.." + +echo "🔍 Checking if environment is ready for new work..." + +# 1. Check symlinks resolve +echo "1️⃣ Checking skill symlinks..." +broken_links=$(find "$CAMERAX_DIR/.agents/skills" -type l ! -exec test -e {} \; -print) +if [ -n "$broken_links" ]; then + echo "❌ Error: Found broken symlinks in .agents/skills:" + echo "$broken_links" + exit 1 +fi +echo "✅ All symlinks resolve correctly." + +# 2. Check git state +echo "2️⃣ Checking git state..." +# Check the whole repository git state +if [ -n "$(git status --porcelain)" ]; then + echo "❌ Error: Git working directory is not clean. Please commit or stash your changes before starting new work." + exit 1 +fi +echo "✅ Git working directory is clean." + +# 3. Check dart and flutter +echo "3️⃣ Checking Flutter and Dart..." +if ! command -v flutter &> /dev/null; then + echo "❌ Error: 'flutter' is not on the PATH." + exit 1 +fi +if ! command -v dart &> /dev/null; then + echo "❌ Error: 'dart' is not on the PATH." + exit 1 +fi +echo "✅ Flutter and Dart are on the PATH." + +# 4. Check dependencies in camera_android_camerax +echo "4️⃣ Checking dependencies in camera_android_camerax..." +cd "$CAMERAX_DIR" +if ! flutter pub get; then + echo "❌ Error: Failed to resolve dependencies." + exit 1 +fi +echo "✅ Dependencies are resolved and ready." + +echo "🎉 Environment is fully ready!" diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 34f4a7445e22..302481d26894 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -38,6 +38,7 @@ dev_dependencies: logging: ^1.2.0 mockito: ^5.4.4 pigeon: ^26.1.4 + yaml: ^3.1.3 topics: - camera diff --git a/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart b/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart index 19a72cb437e2..fa0cb232b17a 100644 --- a/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart +++ b/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart @@ -4,10 +4,8 @@ import 'dart:io'; -import 'package:dart_skills_lint/src/config_parser.dart'; -import 'package:dart_skills_lint/src/models/analysis_severity.dart'; -import 'package:dart_skills_lint/src/validation_session.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:yaml/yaml.dart'; void main() { test('all tracked skills have prevent-skills-sh-publishing rule explicitly configured', () async { @@ -17,45 +15,71 @@ void main() { // to prevent accidental publishing. // 1. Get tracked files using git ls-files - final ProcessResult processResult = await Process.run('git', ['ls-files', '.agents/skills']); + final processResult = await Process.run('git', ['ls-files']); expect(processResult.exitCode, 0, reason: 'git ls-files should succeed'); final output = processResult.stdout as String; - final Iterable lines = output.split('\n').where((line) => line.trim().isNotEmpty); + final lines = output.split('\n').where((line) => line.trim().isNotEmpty); final trackedSkillDirs = {}; for (final line in lines) { - final List parts = line.split('/'); + final parts = line.split('/'); + final agentsIndex = parts.indexOf('.agents'); // We look for files inside .agents/skills// - // parts[0] is .agents, parts[1] is skills - if (parts.length >= 4 && parts[0] == '.agents' && parts[1] == 'skills') { - trackedSkillDirs.add(parts[2]); + // So parts length must be at least agentsIndex + 4 + if (agentsIndex != -1 && + agentsIndex + 3 < parts.length && + parts[agentsIndex + 1] == 'skills') { + trackedSkillDirs.add(parts[agentsIndex + 2]); } } expect(trackedSkillDirs, isNotEmpty, reason: 'Should find at least one tracked skill'); - // 2. Parse configuration - final Configuration config = await ConfigParser.loadConfig(); - final session = ValidationSession( - config: config, - resolvedRules: {}, - ignoreFileOverride: null, - customRules: const [], - printWarnings: false, - fastFail: false, - quiet: true, - generateBaseline: false, - fix: false, - fixApply: false, - ); + // 2. Parse configuration manually to avoid internal API imports + final yamlFile = File('dart_skills_lint.yaml'); + final yamlConfig = loadYaml(yamlFile.readAsStringSync()) as YamlMap; + final toolConfig = yamlConfig['dart_skills_lint'] as YamlMap?; + expect(toolConfig, isNotNull, reason: 'dart_skills_lint config missing'); for (final skillDir in trackedSkillDirs) { final expectedPath = '.agents/skills/$skillDir'; - final Map resolvedRules = session.resolveRulesForPath(expectedPath); + bool hasRule = false; + + // Check directories + final dirs = toolConfig!['directories'] as YamlList?; + if (dirs != null) { + for (final dynamic dir in dirs) { + if (dir is YamlMap) { + final path = dir['path'] as String?; + if (path == expectedPath || path == '.agents/skills') { + final rules = dir['rules'] as YamlMap?; + if (rules != null && rules['prevent-skills-sh-publishing'] != null) { + hasRule = true; + } + } + } + } + } + + // Check individual_skills + final individualSkills = toolConfig['individual_skills'] as YamlList?; + if (individualSkills != null) { + for (final dynamic skill in individualSkills) { + if (skill is YamlMap) { + final path = skill['path'] as String?; + if (path == expectedPath) { + final rules = skill['rules'] as YamlMap?; + if (rules != null && rules['prevent-skills-sh-publishing'] != null) { + hasRule = true; + } + } + } + } + } expect( - resolvedRules.containsKey('prevent-skills-sh-publishing'), + hasRule, isTrue, reason: 'The tracked skill "$skillDir" must have "prevent-skills-sh-publishing" explicitly configured in dart_skills_lint.yaml.', diff --git a/third_party/README.md b/third_party/README.md index 7f619e213993..3e6dbcae9318 100644 --- a/third_party/README.md +++ b/third_party/README.md @@ -16,7 +16,7 @@ When adding a new remote skill, you must follow this exact structure and process 3. **License Requirement:** You **must** download and retain the original `LICENSE` file from the remote repository and place it directly inside the `third_party/` subfolder. -4. **Symlinking:** Once installed, symlink the newly downloaded skill into the appropriate project's `.agents/skills` directory using a relative symlink. +4. **Symlinking:** Once installed, symlink the newly downloaded skill into the appropriate plugin's `.agents/skills` directory using a relative symlink. ```bash cd packages//.agents/skills ln -s ../../../../../third_party//.agents/skills/ diff --git a/third_party/anthropics-skills/LICENSE b/third_party/anthropics-skills/LICENSE deleted file mode 100644 index 1becba2bb0a8..000000000000 --- a/third_party/anthropics-skills/LICENSE +++ /dev/null @@ -1 +0,0 @@ -404: Not Found \ No newline at end of file diff --git a/third_party/anthropics-skills/skills-lock.json b/third_party/anthropics-skills/skills-lock.json deleted file mode 100644 index 1cbfc01ccc6b..000000000000 --- a/third_party/anthropics-skills/skills-lock.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": 1, - "skills": { - "skill-creator": { - "source": "anthropics/skills", - "sourceType": "github", - "skillPath": "skills/skill-creator/SKILL.md", - "computedHash": "5ea13a6d9f0d4bb694405d79acd00cadec0d21bb138c4dd10fcf3c500cb835c2" - } - } -} From 9e0d9e5cc9be6c5e15a8441a45f80d8cb27cbb33 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Thu, 18 Jun 2026 18:49:49 -0400 Subject: [PATCH 10/25] test: revert yaml parsing, use public ValidationSession from repo --- .../camera_android_camerax/pubspec.yaml | 5 +- .../test/tracked_skills_publishing_test.dart | 58 ++++++------------- 2 files changed, 19 insertions(+), 44 deletions(-) diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 302481d26894..2c46ae3fe8e3 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -29,16 +29,15 @@ dev_dependencies: build_runner: ^2.2.0 dart_skills_lint: git: - url: https://github.com/flutter/skills.git + url: https://github.com/reidbaker/skills.git path: tool/dart_skills_lint - ref: 8f85e82be6da429980f7cfe84f2f214a06cbfee1 + ref: b8584b28aad66efa3308738e2257999675e52ba5 flutter_test: sdk: flutter leak_tracker_flutter_testing: any logging: ^1.2.0 mockito: ^5.4.4 pigeon: ^26.1.4 - yaml: ^3.1.3 topics: - camera diff --git a/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart b/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart index fa0cb232b17a..a123a9646f87 100644 --- a/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart +++ b/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart @@ -4,8 +4,8 @@ import 'dart:io'; +import 'package:dart_skills_lint/dart_skills_lint.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:yaml/yaml.dart'; void main() { test('all tracked skills have prevent-skills-sh-publishing rule explicitly configured', () async { @@ -26,7 +26,6 @@ void main() { final parts = line.split('/'); final agentsIndex = parts.indexOf('.agents'); // We look for files inside .agents/skills// - // So parts length must be at least agentsIndex + 4 if (agentsIndex != -1 && agentsIndex + 3 < parts.length && parts[agentsIndex + 1] == 'skills') { @@ -36,50 +35,27 @@ void main() { expect(trackedSkillDirs, isNotEmpty, reason: 'Should find at least one tracked skill'); - // 2. Parse configuration manually to avoid internal API imports - final yamlFile = File('dart_skills_lint.yaml'); - final yamlConfig = loadYaml(yamlFile.readAsStringSync()) as YamlMap; - final toolConfig = yamlConfig['dart_skills_lint'] as YamlMap?; - expect(toolConfig, isNotNull, reason: 'dart_skills_lint config missing'); + // 2. Parse configuration + final config = await ConfigParser.loadConfig(); + final session = ValidationSession( + config: config, + resolvedRules: {}, + ignoreFileOverride: null, + customRules: const [], + printWarnings: false, + fastFail: false, + quiet: true, + generateBaseline: false, + fix: false, + fixApply: false, + ); for (final skillDir in trackedSkillDirs) { final expectedPath = '.agents/skills/$skillDir'; - bool hasRule = false; - - // Check directories - final dirs = toolConfig!['directories'] as YamlList?; - if (dirs != null) { - for (final dynamic dir in dirs) { - if (dir is YamlMap) { - final path = dir['path'] as String?; - if (path == expectedPath || path == '.agents/skills') { - final rules = dir['rules'] as YamlMap?; - if (rules != null && rules['prevent-skills-sh-publishing'] != null) { - hasRule = true; - } - } - } - } - } - - // Check individual_skills - final individualSkills = toolConfig['individual_skills'] as YamlList?; - if (individualSkills != null) { - for (final dynamic skill in individualSkills) { - if (skill is YamlMap) { - final path = skill['path'] as String?; - if (path == expectedPath) { - final rules = skill['rules'] as YamlMap?; - if (rules != null && rules['prevent-skills-sh-publishing'] != null) { - hasRule = true; - } - } - } - } - } + final resolvedRules = session.resolveRulesForPath(expectedPath); expect( - hasRule, + resolvedRules.containsKey('prevent-skills-sh-publishing'), isTrue, reason: 'The tracked skill "$skillDir" must have "prevent-skills-sh-publishing" explicitly configured in dart_skills_lint.yaml.', From 6b68664c0a22f2952b2d97180364d1bf86cb3fac Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Thu, 18 Jun 2026 18:50:52 -0400 Subject: [PATCH 11/25] test: revert to ValidationSession and use ignore implementation_imports --- packages/camera/camera_android_camerax/pubspec.yaml | 4 ++-- .../test/tracked_skills_publishing_test.dart | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 2c46ae3fe8e3..34f4a7445e22 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -29,9 +29,9 @@ dev_dependencies: build_runner: ^2.2.0 dart_skills_lint: git: - url: https://github.com/reidbaker/skills.git + url: https://github.com/flutter/skills.git path: tool/dart_skills_lint - ref: b8584b28aad66efa3308738e2257999675e52ba5 + ref: 8f85e82be6da429980f7cfe84f2f214a06cbfee1 flutter_test: sdk: flutter leak_tracker_flutter_testing: any diff --git a/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart b/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart index a123a9646f87..aebd5c0a59ac 100644 --- a/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart +++ b/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart @@ -4,7 +4,12 @@ import 'dart:io'; -import 'package:dart_skills_lint/dart_skills_lint.dart'; +// ignore: implementation_imports +import 'package:dart_skills_lint/src/config_parser.dart'; +// ignore: implementation_imports +import 'package:dart_skills_lint/src/models/analysis_severity.dart'; +// ignore: implementation_imports +import 'package:dart_skills_lint/src/validation_session.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { From 0942453f4b23b4249f3f6a87804ac6544cc8dbca Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Thu, 18 Jun 2026 18:58:22 -0400 Subject: [PATCH 12/25] test: replace yaml parsing with custom skill rule EnforceTrackedSkillsInternalRule --- .../test/tracked_skills_publishing_test.dart | 70 ------------------- .../test/validate_skills_test.dart | 47 +++++++++++++ 2 files changed, 47 insertions(+), 70 deletions(-) delete mode 100644 packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart diff --git a/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart b/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart deleted file mode 100644 index aebd5c0a59ac..000000000000 --- a/packages/camera/camera_android_camerax/test/tracked_skills_publishing_test.dart +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'dart:io'; - -// ignore: implementation_imports -import 'package:dart_skills_lint/src/config_parser.dart'; -// ignore: implementation_imports -import 'package:dart_skills_lint/src/models/analysis_severity.dart'; -// ignore: implementation_imports -import 'package:dart_skills_lint/src/validation_session.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test('all tracked skills have prevent-skills-sh-publishing rule explicitly configured', () async { - // Explanation: - // Any skill in .agents/skills/ that is checked into version control is considered an internal skill. - // It must explicitly have the `prevent-skills-sh-publishing` rule configured in dart_skills_lint.yaml - // to prevent accidental publishing. - - // 1. Get tracked files using git ls-files - final processResult = await Process.run('git', ['ls-files']); - expect(processResult.exitCode, 0, reason: 'git ls-files should succeed'); - - final output = processResult.stdout as String; - final lines = output.split('\n').where((line) => line.trim().isNotEmpty); - - final trackedSkillDirs = {}; - for (final line in lines) { - final parts = line.split('/'); - final agentsIndex = parts.indexOf('.agents'); - // We look for files inside .agents/skills// - if (agentsIndex != -1 && - agentsIndex + 3 < parts.length && - parts[agentsIndex + 1] == 'skills') { - trackedSkillDirs.add(parts[agentsIndex + 2]); - } - } - - expect(trackedSkillDirs, isNotEmpty, reason: 'Should find at least one tracked skill'); - - // 2. Parse configuration - final config = await ConfigParser.loadConfig(); - final session = ValidationSession( - config: config, - resolvedRules: {}, - ignoreFileOverride: null, - customRules: const [], - printWarnings: false, - fastFail: false, - quiet: true, - generateBaseline: false, - fix: false, - fixApply: false, - ); - - for (final skillDir in trackedSkillDirs) { - final expectedPath = '.agents/skills/$skillDir'; - final resolvedRules = session.resolveRulesForPath(expectedPath); - - expect( - resolvedRules.containsKey('prevent-skills-sh-publishing'), - isTrue, - reason: - 'The tracked skill "$skillDir" must have "prevent-skills-sh-publishing" explicitly configured in dart_skills_lint.yaml.', - ); - } - }); -} diff --git a/packages/camera/camera_android_camerax/test/validate_skills_test.dart b/packages/camera/camera_android_camerax/test/validate_skills_test.dart index 3204d4c67925..4f5ed476369e 100644 --- a/packages/camera/camera_android_camerax/test/validate_skills_test.dart +++ b/packages/camera/camera_android_camerax/test/validate_skills_test.dart @@ -1,10 +1,56 @@ import 'dart:async'; +import 'dart:io'; import 'package:dart_skills_lint/dart_skills_lint.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:logging/logging.dart'; +class EnforceTrackedSkillsInternalRule extends SkillRule { + @override + String get name => 'enforce-tracked-skills-internal'; + + @override + AnalysisSeverity get severity => AnalysisSeverity.error; + + @override + Future> validate(SkillContext context) async { + // Check if any files in the skill directory are tracked in git + final ProcessResult processResult = await Process.run('git', ['ls-files', context.directory.path]); + final String output = (processResult.stdout as String).trim(); + if (output.isEmpty) { + // Not tracked by git, no enforcement needed + return []; + } + + final Object? yaml = context.parsedYaml; + if (yaml == null) { + return [ + ValidationError( + ruleId: name, + severity: severity, + file: 'SKILL.md', + message: 'Tracked skills must have "metadata: internal: true" in SKILL.md frontmatter.', + ) + ]; + } + + final Object? metadata = (yaml as Map)['metadata']; + if (metadata is! Map || metadata['internal'] != true) { + return [ + ValidationError( + ruleId: name, + severity: severity, + file: 'SKILL.md', + message: 'Tracked skills must have "metadata: internal: true" in SKILL.md frontmatter.', + ) + ]; + } + + return []; + } +} + void main() { test('Validate Skills', () async { final Level oldLevel = Logger.root.level; @@ -17,6 +63,7 @@ void main() { final Configuration config = await ConfigParser.loadConfig(); final bool isValid = await validateSkills( config: config, + customRules: [EnforceTrackedSkillsInternalRule()], ); expect(isValid, isTrue, reason: 'Skills validation failed. See above for details.'); } finally { From 9471107cb40abf65c6295688b407392bddc518b9 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Thu, 18 Jun 2026 18:59:43 -0400 Subject: [PATCH 13/25] test: extract EnforceTrackedSkillsInternalRule to its own file with docs --- .../enforce_tracked_skills_internal_rule.dart | 60 +++++++++++++++++++ .../test/validate_skills_test.dart | 46 +------------- 2 files changed, 61 insertions(+), 45 deletions(-) create mode 100644 packages/camera/camera_android_camerax/test/enforce_tracked_skills_internal_rule.dart diff --git a/packages/camera/camera_android_camerax/test/enforce_tracked_skills_internal_rule.dart b/packages/camera/camera_android_camerax/test/enforce_tracked_skills_internal_rule.dart new file mode 100644 index 000000000000..d9d1aa465078 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/enforce_tracked_skills_internal_rule.dart @@ -0,0 +1,60 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:dart_skills_lint/dart_skills_lint.dart'; + +/// A custom lint rule that enforces that all skills tracked in version control +/// are marked as internal. +/// +/// This rule is specifically used to prevent the accidental publishing of +/// workspace-specific agent skills to the global public skills registry. It +/// uses `git ls-files` to determine if a skill directory is checked into git, +/// and if it is, strictly requires that the `metadata: internal: true` +/// frontmatter is present in the `SKILL.md` file. +class EnforceTrackedSkillsInternalRule extends SkillRule { + @override + String get name => 'enforce-tracked-skills-internal'; + + @override + AnalysisSeverity get severity => AnalysisSeverity.error; + + @override + Future> validate(SkillContext context) async { + // Check if any files in the skill directory are tracked in git + final ProcessResult processResult = await Process.run('git', ['ls-files', context.directory.path]); + final String output = (processResult.stdout as String).trim(); + if (output.isEmpty) { + // Not tracked by git, no enforcement needed + return []; + } + + final Object? yaml = context.parsedYaml; + if (yaml == null) { + return [ + ValidationError( + ruleId: name, + severity: severity, + file: 'SKILL.md', + message: 'Tracked skills must have "metadata: internal: true" in SKILL.md frontmatter.', + ) + ]; + } + + final Object? metadata = (yaml as Map)['metadata']; + if (metadata is! Map || metadata['internal'] != true) { + return [ + ValidationError( + ruleId: name, + severity: severity, + file: 'SKILL.md', + message: 'Tracked skills must have "metadata: internal: true" in SKILL.md frontmatter.', + ) + ]; + } + + return []; + } +} diff --git a/packages/camera/camera_android_camerax/test/validate_skills_test.dart b/packages/camera/camera_android_camerax/test/validate_skills_test.dart index 4f5ed476369e..f395c542384b 100644 --- a/packages/camera/camera_android_camerax/test/validate_skills_test.dart +++ b/packages/camera/camera_android_camerax/test/validate_skills_test.dart @@ -1,55 +1,11 @@ import 'dart:async'; -import 'dart:io'; import 'package:dart_skills_lint/dart_skills_lint.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:logging/logging.dart'; -class EnforceTrackedSkillsInternalRule extends SkillRule { - @override - String get name => 'enforce-tracked-skills-internal'; - - @override - AnalysisSeverity get severity => AnalysisSeverity.error; - - @override - Future> validate(SkillContext context) async { - // Check if any files in the skill directory are tracked in git - final ProcessResult processResult = await Process.run('git', ['ls-files', context.directory.path]); - final String output = (processResult.stdout as String).trim(); - if (output.isEmpty) { - // Not tracked by git, no enforcement needed - return []; - } - - final Object? yaml = context.parsedYaml; - if (yaml == null) { - return [ - ValidationError( - ruleId: name, - severity: severity, - file: 'SKILL.md', - message: 'Tracked skills must have "metadata: internal: true" in SKILL.md frontmatter.', - ) - ]; - } - - final Object? metadata = (yaml as Map)['metadata']; - if (metadata is! Map || metadata['internal'] != true) { - return [ - ValidationError( - ruleId: name, - severity: severity, - file: 'SKILL.md', - message: 'Tracked skills must have "metadata: internal: true" in SKILL.md frontmatter.', - ) - ]; - } - - return []; - } -} +import 'enforce_tracked_skills_internal_rule.dart'; void main() { test('Validate Skills', () async { From c4e832f643d839cd16aeaecb3d9334f1045c9d36 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Thu, 18 Jun 2026 19:48:00 -0400 Subject: [PATCH 14/25] ci: allow dart_skills_lint in packages --- .repo_tool_config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.repo_tool_config.yaml b/.repo_tool_config.yaml index 7ac843e8901e..7797e87a90fc 100644 --- a/.repo_tool_config.yaml +++ b/.repo_tool_config.yaml @@ -113,6 +113,7 @@ allowed_dependencies: # Google-owned packages - _discoveryapis_commons - adaptive_navigation + - dart_skills_lint - googleapis - googleapis_auth - json_annotation From acc34a504c3b3eb43693fafbac2612c919aca0f9 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Thu, 18 Jun 2026 19:56:45 -0400 Subject: [PATCH 15/25] test: fix internal rule validation feedback --- .../enforce_tracked_skills_internal_rule.dart | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/packages/camera/camera_android_camerax/test/enforce_tracked_skills_internal_rule.dart b/packages/camera/camera_android_camerax/test/enforce_tracked_skills_internal_rule.dart index d9d1aa465078..a3b02e9abf0b 100644 --- a/packages/camera/camera_android_camerax/test/enforce_tracked_skills_internal_rule.dart +++ b/packages/camera/camera_android_camerax/test/enforce_tracked_skills_internal_rule.dart @@ -6,13 +6,13 @@ import 'dart:io'; import 'package:dart_skills_lint/dart_skills_lint.dart'; -/// A custom lint rule that enforces that all skills tracked in version control -/// are marked as internal. +/// A custom lint rule that enforces that all skills tracked in version control +/// are marked as internal. /// -/// This rule is specifically used to prevent the accidental publishing of -/// workspace-specific agent skills to the global public skills registry. It +/// This rule is specifically used to prevent the accidental publishing of +/// workspace-specific agent skills to the global public skills registry. It /// uses `git ls-files` to determine if a skill directory is checked into git, -/// and if it is, strictly requires that the `metadata: internal: true` +/// and if it is, strictly requires that the `metadata: internal: true` /// frontmatter is present in the `SKILL.md` file. class EnforceTrackedSkillsInternalRule extends SkillRule { @override @@ -24,7 +24,23 @@ class EnforceTrackedSkillsInternalRule extends SkillRule { @override Future> validate(SkillContext context) async { // Check if any files in the skill directory are tracked in git - final ProcessResult processResult = await Process.run('git', ['ls-files', context.directory.path]); + final ProcessResult processResult; + try { + processResult = await Process.run('git', ['ls-files', context.directory.path]); + } on ProcessException catch (e) { + return [ + ValidationError( + ruleId: name, + severity: severity, + file: 'SKILL.md', + message: + 'Failed to run git to check tracked status. Is git installed and on the PATH? Error: $e', + ), + ]; + } + if (processResult.exitCode != 0) { + return []; + } final String output = (processResult.stdout as String).trim(); if (output.isEmpty) { // Not tracked by git, no enforcement needed @@ -32,18 +48,18 @@ class EnforceTrackedSkillsInternalRule extends SkillRule { } final Object? yaml = context.parsedYaml; - if (yaml == null) { + if (yaml is! Map) { return [ ValidationError( ruleId: name, severity: severity, file: 'SKILL.md', message: 'Tracked skills must have "metadata: internal: true" in SKILL.md frontmatter.', - ) + ), ]; } - final Object? metadata = (yaml as Map)['metadata']; + final Object? metadata = yaml['metadata']; if (metadata is! Map || metadata['internal'] != true) { return [ ValidationError( @@ -51,7 +67,7 @@ class EnforceTrackedSkillsInternalRule extends SkillRule { severity: severity, file: 'SKILL.md', message: 'Tracked skills must have "metadata: internal: true" in SKILL.md frontmatter.', - ) + ), ]; } From 288e5ec08e403828f60cd0ff30af8979cbe5673d Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Fri, 19 Jun 2026 13:56:33 -0400 Subject: [PATCH 16/25] Fix missing copyright headers in check-readiness agent skill --- .../.agents/skills/check-readiness/scripts/check.sh | 3 +++ .../test/enforce_tracked_skills_internal_rule.dart | 6 +++--- .../camera_android_camerax/test/validate_skills_test.dart | 4 ++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/camera/camera_android_camerax/.agents/skills/check-readiness/scripts/check.sh b/packages/camera/camera_android_camerax/.agents/skills/check-readiness/scripts/check.sh index d6a2b09e1eb1..beeb6482f8c9 100755 --- a/packages/camera/camera_android_camerax/.agents/skills/check-readiness/scripts/check.sh +++ b/packages/camera/camera_android_camerax/.agents/skills/check-readiness/scripts/check.sh @@ -1,4 +1,7 @@ #!/bin/bash +# Copyright 2013 The Flutter Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. # Stop on first error set -e diff --git a/packages/camera/camera_android_camerax/test/enforce_tracked_skills_internal_rule.dart b/packages/camera/camera_android_camerax/test/enforce_tracked_skills_internal_rule.dart index a3b02e9abf0b..8d6216b3a368 100644 --- a/packages/camera/camera_android_camerax/test/enforce_tracked_skills_internal_rule.dart +++ b/packages/camera/camera_android_camerax/test/enforce_tracked_skills_internal_rule.dart @@ -1,6 +1,6 @@ -// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. import 'dart:io'; diff --git a/packages/camera/camera_android_camerax/test/validate_skills_test.dart b/packages/camera/camera_android_camerax/test/validate_skills_test.dart index f395c542384b..9f94399f4791 100644 --- a/packages/camera/camera_android_camerax/test/validate_skills_test.dart +++ b/packages/camera/camera_android_camerax/test/validate_skills_test.dart @@ -1,3 +1,7 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + import 'dart:async'; import 'package:dart_skills_lint/dart_skills_lint.dart'; From 43be3d10fdbad80c24c237d55886bc30274984b7 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Fri, 19 Jun 2026 14:32:52 -0400 Subject: [PATCH 17/25] Support .pubignore in publish-check --- .../camera/camera_android_camerax/.pubignore | 1 + .../tool/lib/src/publish_check_command.dart | 91 ++++++++++++++++++- 2 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 packages/camera/camera_android_camerax/.pubignore diff --git a/packages/camera/camera_android_camerax/.pubignore b/packages/camera/camera_android_camerax/.pubignore new file mode 100644 index 000000000000..af0c92a5fc3b --- /dev/null +++ b/packages/camera/camera_android_camerax/.pubignore @@ -0,0 +1 @@ +.agents/ diff --git a/script/tool/lib/src/publish_check_command.dart b/script/tool/lib/src/publish_check_command.dart index da68f9da5c88..676797cb1c16 100644 --- a/script/tool/lib/src/publish_check_command.dart +++ b/script/tool/lib/src/publish_check_command.dart @@ -190,20 +190,103 @@ class PublishCheckCommand extends PackageLoopingCommand { return true; } - if (!getBoolArg(_allowPrereleaseFlag)) { - return false; - } - await stdOutCompleter.future; await stdInCompleter.future; final output = outputBuffer.toString(); + + if (await process.exitCode == 65 && _onlyPubignoreWarnings(output, package)) { + print('Ignoring warning about gitignored files because they are in .pubignore.'); + return true; + } + + if (!getBoolArg(_allowPrereleaseFlag)) { + return false; + } + return output.contains('Package has 1 warning') && output.contains( 'Packages with an SDK constraint on a pre-release of the Dart SDK should themselves be published as a pre-release version.', ); } + bool _onlyPubignoreWarnings(String output, RepositoryPackage package) { + if (!output.contains('Files that are checked in while gitignored:')) { + return false; + } + + final RegExp warningCountRegex = RegExp(r'Package has (\d+) warning'); + final Match? match = warningCountRegex.firstMatch(output); + if (match != null) { + final int count = int.parse(match.group(1)!); + if (count > 1) { + return false; + } + } + + final List lines = output.split('\n'); + bool inFilesSection = false; + final List gitignoredFiles = []; + + for (final String line in lines) { + if (line.trim() == 'Files that are checked in while gitignored:') { + inFilesSection = true; + continue; + } + if (inFilesSection) { + if (line.trim().isEmpty) { + continue; + } + if (line.startsWith(' ') && !line.startsWith(' ')) { + gitignoredFiles.add(line.trim()); + } else if (!line.startsWith(' ')) { + inFilesSection = false; + } + } + } + + if (gitignoredFiles.isEmpty) { + return false; + } + + final io.File pubignoreFile = package.directory.childFile('.pubignore'); + if (!pubignoreFile.existsSync()) { + return false; + } + + final List pubignoreLines = pubignoreFile + .readAsLinesSync() + .map((String line) => line.trim()) + .where((String line) => line.isNotEmpty && !line.startsWith('#')) + .toList(); + + for (final String file in gitignoredFiles) { + bool isIgnored = false; + for (final String ignoreRule in pubignoreLines) { + String rule = ignoreRule; + if (rule.startsWith('/')) { + rule = rule.substring(1); + } + if (rule.endsWith('/')) { + if (file.startsWith(rule)) { + isIgnored = true; + break; + } + } else { + if (file == rule || file.startsWith('$rule/')) { + isIgnored = true; + break; + } + } + } + if (!isIgnored) { + return false; + } + } + + return true; + } + /// Returns true if the package has been explicitly marked as not for /// publishing. bool _isMarkedAsUnpublishable(RepositoryPackage package) { From 8d1794901b33260504e2be42db220ec821afb81e Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Fri, 19 Jun 2026 14:41:28 -0400 Subject: [PATCH 18/25] Add tests for publish_check pubignore parsing --- .../tool/test/publish_check_command_test.dart | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/script/tool/test/publish_check_command_test.dart b/script/tool/test/publish_check_command_test.dart index 3b76c9c79f48..19f39fc31fe8 100644 --- a/script/tool/test/publish_check_command_test.dart +++ b/script/tool/test/publish_check_command_test.dart @@ -258,6 +258,70 @@ void main() { expect(output, isNot(contains(contains('ERROR:')))); }); + test('pass when gitignored files are covered by .pubignore', () async { + final RepositoryPackage package = createFakePlugin('d', packagesDir); + final File pubignoreFile = package.directory.childFile('.pubignore'); + pubignoreFile.writeAsStringSync('.agents/\n'); + + final MockProcess process = MockProcess( + exitCode: 65, + stderr: + 'Package has 1 warning.\n' + 'Files that are checked in while gitignored:\n' + ' .agents/skills/something.md\n', + ); + processRunner.mockProcessesForExecutable['flutter'] = [ + FakeProcessInfo(MockProcess(), ['pub', 'get']), + FakeProcessInfo(process, ['pub', 'publish']), + ]; + + final List output = await runCapturingPrint(runner, ['publish-check']); + + expect( + output, + containsAllInOrder([ + contains('Ignoring warning about gitignored files because they are in .pubignore.'), + ]), + ); + }); + + test('fail when gitignored files are NOT covered by .pubignore', () async { + final RepositoryPackage package = createFakePlugin('d', packagesDir); + final File pubignoreFile = package.directory.childFile('.pubignore'); + pubignoreFile.writeAsStringSync('.agents/\n'); + + final MockProcess process = MockProcess( + exitCode: 65, + stderr: + 'Package has 1 warning.\n' + 'Files that are checked in while gitignored:\n' + ' other_folder/skills/something.md\n', + ); + processRunner.mockProcessesForExecutable['flutter'] = [ + FakeProcessInfo(MockProcess(), ['pub', 'get']), + FakeProcessInfo(process, ['pub', 'publish']), + ]; + + Error? commandError; + final List output = await runCapturingPrint( + runner, + ['publish-check'], + errorHandler: (Error e) { + commandError = e; + }, + ); + + expect(commandError, isA()); + expect( + output, + isNot( + contains( + contains('Ignoring warning about gitignored files because they are in .pubignore.'), + ), + ), + ); + }); + test( 'runs validation even for packages that are already published and reports failure', () async { From 81c1c5236c9a7af0076111d4ba5e4ccaf1ea78b2 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Fri, 19 Jun 2026 14:53:02 -0400 Subject: [PATCH 19/25] Fix .gitignore for check-readiness skill and symlinked skills --- .../camera_android_camerax/.agents/skills/.gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/camera/camera_android_camerax/.agents/skills/.gitignore b/packages/camera/camera_android_camerax/.agents/skills/.gitignore index f70d8ee99781..07139a7c2b16 100644 --- a/packages/camera/camera_android_camerax/.agents/skills/.gitignore +++ b/packages/camera/camera_android_camerax/.agents/skills/.gitignore @@ -4,6 +4,14 @@ # Un-ignore specific checked-in skills # (Add specific contributor skills here as they are created) !check-readiness/ +!check-readiness/** +!dart-add-unit-test +!dart-collect-coverage +!dart-generate-test-mocks +!dart-run-static-analysis +!flutter-add-integration-test +!grill-me +!receiving-code-review # Keep essential configuration and docs !.gitignore From 438537cf2bff65033ff8ee3f47eb309353a70d9a Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Fri, 19 Jun 2026 14:53:25 -0400 Subject: [PATCH 20/25] Revert un-ignoring of symlinked skills --- .../camera_android_camerax/.agents/skills/.gitignore | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/camera/camera_android_camerax/.agents/skills/.gitignore b/packages/camera/camera_android_camerax/.agents/skills/.gitignore index 07139a7c2b16..64c05661c733 100644 --- a/packages/camera/camera_android_camerax/.agents/skills/.gitignore +++ b/packages/camera/camera_android_camerax/.agents/skills/.gitignore @@ -5,13 +5,6 @@ # (Add specific contributor skills here as they are created) !check-readiness/ !check-readiness/** -!dart-add-unit-test -!dart-collect-coverage -!dart-generate-test-mocks -!dart-run-static-analysis -!flutter-add-integration-test -!grill-me -!receiving-code-review # Keep essential configuration and docs !.gitignore From f64190b1f46de7389abe2c397b03e27c7a954c9b Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Fri, 19 Jun 2026 14:53:49 -0400 Subject: [PATCH 21/25] Add TODO for tracking dart-lang/pub issue 4841 --- script/tool/lib/src/publish_check_command.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/script/tool/lib/src/publish_check_command.dart b/script/tool/lib/src/publish_check_command.dart index 676797cb1c16..550811430157 100644 --- a/script/tool/lib/src/publish_check_command.dart +++ b/script/tool/lib/src/publish_check_command.dart @@ -195,6 +195,8 @@ class PublishCheckCommand extends PackageLoopingCommand { final output = outputBuffer.toString(); + // TODO(reidbaker): Remove this custom error handling for gitignored files + // once https://github.com/dart-lang/pub/issues/4841 is resolved. if (await process.exitCode == 65 && _onlyPubignoreWarnings(output, package)) { print('Ignoring warning about gitignored files because they are in .pubignore.'); return true; From 14d7cfcb8ab232186da90d181283ccfc2b92968d Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Fri, 19 Jun 2026 14:56:29 -0400 Subject: [PATCH 22/25] Group pubignore tracking tests under issue 4841 --- .../tool/test/publish_check_command_test.dart | 129 +++++++++--------- 1 file changed, 67 insertions(+), 62 deletions(-) diff --git a/script/tool/test/publish_check_command_test.dart b/script/tool/test/publish_check_command_test.dart index 19f39fc31fe8..ab9567c0c80b 100644 --- a/script/tool/test/publish_check_command_test.dart +++ b/script/tool/test/publish_check_command_test.dart @@ -258,69 +258,74 @@ void main() { expect(output, isNot(contains(contains('ERROR:')))); }); - test('pass when gitignored files are covered by .pubignore', () async { - final RepositoryPackage package = createFakePlugin('d', packagesDir); - final File pubignoreFile = package.directory.childFile('.pubignore'); - pubignoreFile.writeAsStringSync('.agents/\n'); - - final MockProcess process = MockProcess( - exitCode: 65, - stderr: - 'Package has 1 warning.\n' - 'Files that are checked in while gitignored:\n' - ' .agents/skills/something.md\n', - ); - processRunner.mockProcessesForExecutable['flutter'] = [ - FakeProcessInfo(MockProcess(), ['pub', 'get']), - FakeProcessInfo(process, ['pub', 'publish']), - ]; - - final List output = await runCapturingPrint(runner, ['publish-check']); - - expect( - output, - containsAllInOrder([ - contains('Ignoring warning about gitignored files because they are in .pubignore.'), - ]), - ); - }); - - test('fail when gitignored files are NOT covered by .pubignore', () async { - final RepositoryPackage package = createFakePlugin('d', packagesDir); - final File pubignoreFile = package.directory.childFile('.pubignore'); - pubignoreFile.writeAsStringSync('.agents/\n'); - - final MockProcess process = MockProcess( - exitCode: 65, - stderr: - 'Package has 1 warning.\n' - 'Files that are checked in while gitignored:\n' - ' other_folder/skills/something.md\n', - ); - processRunner.mockProcessesForExecutable['flutter'] = [ - FakeProcessInfo(MockProcess(), ['pub', 'get']), - FakeProcessInfo(process, ['pub', 'publish']), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, - ['publish-check'], - errorHandler: (Error e) { - commandError = e; - }, - ); + group( + 'TODO(reidbaker): remove when https://github.com/dart-lang/pub/issues/4841 is resolved', + () { + test('pass when gitignored files are covered by .pubignore', () async { + final RepositoryPackage package = createFakePlugin('d', packagesDir); + final File pubignoreFile = package.directory.childFile('.pubignore'); + pubignoreFile.writeAsStringSync('.agents/\n'); + + final MockProcess process = MockProcess( + exitCode: 65, + stderr: + 'Package has 1 warning.\n' + 'Files that are checked in while gitignored:\n' + ' .agents/skills/something.md\n', + ); + processRunner.mockProcessesForExecutable['flutter'] = [ + FakeProcessInfo(MockProcess(), ['pub', 'get']), + FakeProcessInfo(process, ['pub', 'publish']), + ]; + + final List output = await runCapturingPrint(runner, ['publish-check']); + + expect( + output, + containsAllInOrder([ + contains('Ignoring warning about gitignored files because they are in .pubignore.'), + ]), + ); + }); - expect(commandError, isA()); - expect( - output, - isNot( - contains( - contains('Ignoring warning about gitignored files because they are in .pubignore.'), - ), - ), - ); - }); + test('fail when gitignored files are NOT covered by .pubignore', () async { + final RepositoryPackage package = createFakePlugin('d', packagesDir); + final File pubignoreFile = package.directory.childFile('.pubignore'); + pubignoreFile.writeAsStringSync('.agents/\n'); + + final MockProcess process = MockProcess( + exitCode: 65, + stderr: + 'Package has 1 warning.\n' + 'Files that are checked in while gitignored:\n' + ' other_folder/skills/something.md\n', + ); + processRunner.mockProcessesForExecutable['flutter'] = [ + FakeProcessInfo(MockProcess(), ['pub', 'get']), + FakeProcessInfo(process, ['pub', 'publish']), + ]; + + Error? commandError; + final List output = await runCapturingPrint( + runner, + ['publish-check'], + errorHandler: (Error e) { + commandError = e; + }, + ); + + expect(commandError, isA()); + expect( + output, + isNot( + contains( + contains('Ignoring warning about gitignored files because they are in .pubignore.'), + ), + ), + ); + }); + }, + ); test( 'runs validation even for packages that are already published and reports failure', From d7f024e7c0b25d2ceeb7ff7e3bae6d90cb851518 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Fri, 19 Jun 2026 14:56:58 -0400 Subject: [PATCH 23/25] Move TODO out of group name --- .../tool/test/publish_check_command_test.dart | 130 +++++++++--------- 1 file changed, 64 insertions(+), 66 deletions(-) diff --git a/script/tool/test/publish_check_command_test.dart b/script/tool/test/publish_check_command_test.dart index ab9567c0c80b..a37a99412446 100644 --- a/script/tool/test/publish_check_command_test.dart +++ b/script/tool/test/publish_check_command_test.dart @@ -258,74 +258,72 @@ void main() { expect(output, isNot(contains(contains('ERROR:')))); }); - group( - 'TODO(reidbaker): remove when https://github.com/dart-lang/pub/issues/4841 is resolved', - () { - test('pass when gitignored files are covered by .pubignore', () async { - final RepositoryPackage package = createFakePlugin('d', packagesDir); - final File pubignoreFile = package.directory.childFile('.pubignore'); - pubignoreFile.writeAsStringSync('.agents/\n'); - - final MockProcess process = MockProcess( - exitCode: 65, - stderr: - 'Package has 1 warning.\n' - 'Files that are checked in while gitignored:\n' - ' .agents/skills/something.md\n', - ); - processRunner.mockProcessesForExecutable['flutter'] = [ - FakeProcessInfo(MockProcess(), ['pub', 'get']), - FakeProcessInfo(process, ['pub', 'publish']), - ]; - - final List output = await runCapturingPrint(runner, ['publish-check']); - - expect( - output, - containsAllInOrder([ - contains('Ignoring warning about gitignored files because they are in .pubignore.'), - ]), - ); - }); + // TODO(reidbaker): remove when https://github.com/dart-lang/pub/issues/4841 is resolved + group('.pubignore parsing', () { + test('pass when gitignored files are covered by .pubignore', () async { + final RepositoryPackage package = createFakePlugin('d', packagesDir); + final File pubignoreFile = package.directory.childFile('.pubignore'); + pubignoreFile.writeAsStringSync('.agents/\n'); + + final MockProcess process = MockProcess( + exitCode: 65, + stderr: + 'Package has 1 warning.\n' + 'Files that are checked in while gitignored:\n' + ' .agents/skills/something.md\n', + ); + processRunner.mockProcessesForExecutable['flutter'] = [ + FakeProcessInfo(MockProcess(), ['pub', 'get']), + FakeProcessInfo(process, ['pub', 'publish']), + ]; + + final List output = await runCapturingPrint(runner, ['publish-check']); + + expect( + output, + containsAllInOrder([ + contains('Ignoring warning about gitignored files because they are in .pubignore.'), + ]), + ); + }); + + test('fail when gitignored files are NOT covered by .pubignore', () async { + final RepositoryPackage package = createFakePlugin('d', packagesDir); + final File pubignoreFile = package.directory.childFile('.pubignore'); + pubignoreFile.writeAsStringSync('.agents/\n'); + + final MockProcess process = MockProcess( + exitCode: 65, + stderr: + 'Package has 1 warning.\n' + 'Files that are checked in while gitignored:\n' + ' other_folder/skills/something.md\n', + ); + processRunner.mockProcessesForExecutable['flutter'] = [ + FakeProcessInfo(MockProcess(), ['pub', 'get']), + FakeProcessInfo(process, ['pub', 'publish']), + ]; + + Error? commandError; + final List output = await runCapturingPrint( + runner, + ['publish-check'], + errorHandler: (Error e) { + commandError = e; + }, + ); - test('fail when gitignored files are NOT covered by .pubignore', () async { - final RepositoryPackage package = createFakePlugin('d', packagesDir); - final File pubignoreFile = package.directory.childFile('.pubignore'); - pubignoreFile.writeAsStringSync('.agents/\n'); - - final MockProcess process = MockProcess( - exitCode: 65, - stderr: - 'Package has 1 warning.\n' - 'Files that are checked in while gitignored:\n' - ' other_folder/skills/something.md\n', - ); - processRunner.mockProcessesForExecutable['flutter'] = [ - FakeProcessInfo(MockProcess(), ['pub', 'get']), - FakeProcessInfo(process, ['pub', 'publish']), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, - ['publish-check'], - errorHandler: (Error e) { - commandError = e; - }, - ); - - expect(commandError, isA()); - expect( - output, - isNot( - contains( - contains('Ignoring warning about gitignored files because they are in .pubignore.'), - ), + expect(commandError, isA()); + expect( + output, + isNot( + contains( + contains('Ignoring warning about gitignored files because they are in .pubignore.'), ), - ); - }); - }, - ); + ), + ); + }); + }); test( 'runs validation even for packages that are already published and reports failure', From bc37ae6699a127e318739027e0049567de3a3789 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Fri, 19 Jun 2026 14:57:30 -0400 Subject: [PATCH 24/25] Fix analyzer warnings --- script/tool/lib/src/publish_check_command.dart | 16 ++++++++-------- script/tool/test/publish_check_command_test.dart | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/script/tool/lib/src/publish_check_command.dart b/script/tool/lib/src/publish_check_command.dart index 550811430157..839fc7b99f4f 100644 --- a/script/tool/lib/src/publish_check_command.dart +++ b/script/tool/lib/src/publish_check_command.dart @@ -217,7 +217,7 @@ class PublishCheckCommand extends PackageLoopingCommand { return false; } - final RegExp warningCountRegex = RegExp(r'Package has (\d+) warning'); + final warningCountRegex = RegExp(r'Package has (\d+) warning'); final Match? match = warningCountRegex.firstMatch(output); if (match != null) { final int count = int.parse(match.group(1)!); @@ -227,10 +227,10 @@ class PublishCheckCommand extends PackageLoopingCommand { } final List lines = output.split('\n'); - bool inFilesSection = false; - final List gitignoredFiles = []; + var inFilesSection = false; + final gitignoredFiles = []; - for (final String line in lines) { + for (final line in lines) { if (line.trim() == 'Files that are checked in while gitignored:') { inFilesSection = true; continue; @@ -262,10 +262,10 @@ class PublishCheckCommand extends PackageLoopingCommand { .where((String line) => line.isNotEmpty && !line.startsWith('#')) .toList(); - for (final String file in gitignoredFiles) { - bool isIgnored = false; - for (final String ignoreRule in pubignoreLines) { - String rule = ignoreRule; + for (final file in gitignoredFiles) { + var isIgnored = false; + for (final ignoreRule in pubignoreLines) { + var rule = ignoreRule; if (rule.startsWith('/')) { rule = rule.substring(1); } diff --git a/script/tool/test/publish_check_command_test.dart b/script/tool/test/publish_check_command_test.dart index a37a99412446..74677475d4f0 100644 --- a/script/tool/test/publish_check_command_test.dart +++ b/script/tool/test/publish_check_command_test.dart @@ -265,7 +265,7 @@ void main() { final File pubignoreFile = package.directory.childFile('.pubignore'); pubignoreFile.writeAsStringSync('.agents/\n'); - final MockProcess process = MockProcess( + final process = MockProcess( exitCode: 65, stderr: 'Package has 1 warning.\n' @@ -292,7 +292,7 @@ void main() { final File pubignoreFile = package.directory.childFile('.pubignore'); pubignoreFile.writeAsStringSync('.agents/\n'); - final MockProcess process = MockProcess( + final process = MockProcess( exitCode: 65, stderr: 'Package has 1 warning.\n' From 16e3f28f0863936434e93a0f9f54d162760fd7b1 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Fri, 19 Jun 2026 15:15:23 -0400 Subject: [PATCH 25/25] Fix .gitignore un-ignore rule overreach --- .../camera/camera_android_camerax/.agents/skills/.gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera_android_camerax/.agents/skills/.gitignore b/packages/camera/camera_android_camerax/.agents/skills/.gitignore index 64c05661c733..e503a7b5f96d 100644 --- a/packages/camera/camera_android_camerax/.agents/skills/.gitignore +++ b/packages/camera/camera_android_camerax/.agents/skills/.gitignore @@ -4,7 +4,9 @@ # Un-ignore specific checked-in skills # (Add specific contributor skills here as they are created) !check-readiness/ -!check-readiness/** +!check-readiness/SKILL.md +!check-readiness/scripts/ +!check-readiness/scripts/check.sh # Keep essential configuration and docs !.gitignore