From 0fef2c9b37b188161b3624957789f4d69062a7a7 Mon Sep 17 00:00:00 2001 From: Anilcan Cakir Date: Wed, 17 Jun 2026 02:28:25 +0300 Subject: [PATCH 1/3] fix(install): inject magic_devtools dusk barrel, gate on magic_devtools dep dusk:install injected the now-removed package:magic/dusk_integration.dart import (the adapter moved to the magic_devtools package). Repoint the injected import to package:magic_devtools/dusk.dart and gate the magic-side wiring on a magic_devtools dependency (_hasMagicDevtoolsDep) so a magic-only consumer never gets an unresolvable import. --- CHANGELOG.md | 8 +++++++ lib/src/commands/dusk_install_command.dart | 21 ++++++++++--------- .../commands/dusk_install_command_test.dart | 8 +++---- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49902fe..5600a97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ This project follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0. --- +## [Unreleased] + +### Changed + +- **`dusk:install` now injects `import 'package:magic_devtools/dusk.dart';` and gates on the `magic_devtools` dependency** instead of the removed `package:magic/dusk_integration.dart`. The `MagicDuskIntegration` class was extracted from the `magic` core into the new `magic_devtools` package; the injected class name (`MagicDuskIntegration.install()`) is unchanged. Consumers that follow magic's install.yaml (which adds `magic_devtools` to dev_dependencies before running `dusk:install`) get the integration wired automatically; magic-only consumers without `magic_devtools` in pubspec.yaml are unaffected. Coordinated with the magic_devtools extraction. + +--- + ## [0.0.7] - 2026-06-17 ### Added diff --git a/lib/src/commands/dusk_install_command.dart b/lib/src/commands/dusk_install_command.dart index fcaed84..2402cb1 100644 --- a/lib/src/commands/dusk_install_command.dart +++ b/lib/src/commands/dusk_install_command.dart @@ -16,7 +16,7 @@ import 'package:fluttersdk_artisan/artisan.dart'; /// Two anchor modes: /// - Magic-stack apps (`lib/main.dart` contains `await Magic.init(`): /// wire `DuskPlugin.install()` BEFORE `Magic.init` so the driver is -/// live during Magic boot. When `magic:` is also a pubspec dep, ALSO +/// live during Magic boot. When `magic_devtools:` is also a pubspec dep, ALSO /// inject `MagicDuskIntegration.install()` AFTER `Magic.init` (the /// integration queries `Magic.find()` for the form / nav /// enrichers, which only resolves once the container is ready). @@ -45,7 +45,7 @@ class DuskInstallCommand extends ArtisanCommand { static String _defaultMainDartPath() => 'lib/main.dart'; /// Hook for tests to override the resolved `pubspec.yaml` path used - /// for `magic:` dependency detection. + /// for `magic_devtools:` dependency detection. static String Function() pubspecPathResolver = _defaultPubspecPath; static String _defaultPubspecPath() => 'pubspec.yaml'; @@ -177,7 +177,7 @@ class DuskInstallCommand extends ArtisanCommand { /// the canonical install anchor: `await Magic.init(` on /// Magic-stack apps (so dusk is wired before Magic boot side /// effects), otherwise `runApp(` for vanilla Flutter apps. - /// 3. When pubspec has `magic:` AND main.dart has `await Magic.init(`, + /// 3. When pubspec has `magic_devtools:` AND main.dart has `await Magic.init(`, /// inject `MagicDuskIntegration.install()` AFTER that call (the /// integration queries `Magic.find()` for the form / nav /// enrichers, so it must run after the container is ready). @@ -234,12 +234,12 @@ class DuskInstallCommand extends ArtisanCommand { } // 3. Magic-side coordinated wiring when the consumer pulls in magic. - // Detect via pubspec.yaml; skip silently when magic is not a dep + // Detect via pubspec.yaml; skip silently when magic_devtools is not a dep // or when main.dart has no Magic.init() anchor (vanilla app). - if (_hasMagicDep()) { + if (_hasMagicDevtoolsDep()) { MainDartEditor.addImport( mainDartPath, - "import 'package:magic/dusk_integration.dart';", + "import 'package:magic_devtools/dusk.dart';", ); try { MainDartEditor.injectAfterMagicInit( @@ -255,11 +255,12 @@ class DuskInstallCommand extends ArtisanCommand { } } - /// Returns true when the consumer's pubspec.yaml lists `magic:` as a - /// top-level dependency (2-space indent under `dependencies:`). - static bool _hasMagicDep() { + /// Returns true when the consumer's pubspec.yaml lists `magic_devtools:` + /// (the package that ships MagicDuskIntegration) as a dependency or + /// dev_dependency (2-space indent). + static bool _hasMagicDevtoolsDep() { final pubspec = File(pubspecPathResolver()); if (!pubspec.existsSync()) return false; - return RegExp(r'\n magic:').hasMatch(pubspec.readAsStringSync()); + return RegExp(r'\n magic_devtools:').hasMatch(pubspec.readAsStringSync()); } } diff --git a/test/src/commands/dusk_install_command_test.dart b/test/src/commands/dusk_install_command_test.dart index f5c2375..c38b196 100644 --- a/test/src/commands/dusk_install_command_test.dart +++ b/test/src/commands/dusk_install_command_test.dart @@ -158,7 +158,7 @@ class MyApp extends StatelessWidget { () async { final mainDartPath = _seedProject( tempDir, - pubspecDeps: const {'magic': 'any'}, + pubspecDeps: const {'magic': 'any', 'magic_devtools': 'any'}, mainDartContents: ''' import 'package:flutter/material.dart'; import 'package:magic/magic.dart'; @@ -197,10 +197,10 @@ Future main() async { reason: 'MagicDuskIntegration.install() must land AFTER Magic.init()', ); expect( - result.contains("import 'package:magic/dusk_integration.dart';"), + result.contains("import 'package:magic_devtools/dusk.dart';"), isTrue, - reason: 'magic-stack inject must reference the new dusk_integration ' - 'sub-barrel, not the legacy magic.dart main barrel', + reason: 'must reference the magic_devtools dusk barrel, not the ' + 'removed package:magic sub-barrel', ); }, ); From ec7dc31a189314b7ad85088d933ba325f52c47bc Mon Sep 17 00:00:00 2001 From: Anilcan Cakir Date: Wed, 17 Jun 2026 13:11:49 +0300 Subject: [PATCH 2/3] fix(install): only inject magic_devtools import when Magic.init anchor present (PR #20 review) --- CHANGELOG.md | 4 ++ lib/src/commands/dusk_install_command.dart | 6 +- .../commands/dusk_install_command_test.dart | 55 +++++++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5600a97..58ab925 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ This project follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0. ## [Unreleased] +### Fixed + +- **`dusk:install` no longer injects `import 'package:magic_devtools/dusk.dart';` or `MagicDuskIntegration.install()` into a vanilla Flutter app that has `magic_devtools` in its pubspec but no `await Magic.init(` call in `lib/main.dart`.** Previously, the `magic_devtools` wiring block ran whenever the pubspec listed the dependency, regardless of whether a `Magic.init` anchor existed. This left an unused import in the consumer's file, causing `dart analyze` to fail. The gate is now `hasMagicInit && _hasMagicDevtoolsDep()`, matching the block's own intent documented in the comment above it. The existing `try/catch` around `injectAfterMagicInit` is retained as a defensive fallback. + ### Changed - **`dusk:install` now injects `import 'package:magic_devtools/dusk.dart';` and gates on the `magic_devtools` dependency** instead of the removed `package:magic/dusk_integration.dart`. The `MagicDuskIntegration` class was extracted from the `magic` core into the new `magic_devtools` package; the injected class name (`MagicDuskIntegration.install()`) is unchanged. Consumers that follow magic's install.yaml (which adds `magic_devtools` to dev_dependencies before running `dusk:install`) get the integration wired automatically; magic-only consumers without `magic_devtools` in pubspec.yaml are unaffected. Coordinated with the magic_devtools extraction. diff --git a/lib/src/commands/dusk_install_command.dart b/lib/src/commands/dusk_install_command.dart index 2402cb1..e0bca66 100644 --- a/lib/src/commands/dusk_install_command.dart +++ b/lib/src/commands/dusk_install_command.dart @@ -236,7 +236,7 @@ class DuskInstallCommand extends ArtisanCommand { // 3. Magic-side coordinated wiring when the consumer pulls in magic. // Detect via pubspec.yaml; skip silently when magic_devtools is not a dep // or when main.dart has no Magic.init() anchor (vanilla app). - if (_hasMagicDevtoolsDep()) { + if (hasMagicInit && _hasMagicDevtoolsDep()) { MainDartEditor.addImport( mainDartPath, "import 'package:magic_devtools/dusk.dart';", @@ -256,8 +256,8 @@ class DuskInstallCommand extends ArtisanCommand { } /// Returns true when the consumer's pubspec.yaml lists `magic_devtools:` - /// (the package that ships MagicDuskIntegration) as a dependency or - /// dev_dependency (2-space indent). + /// (the package that ships MagicDuskIntegration) under `dependencies:` or + /// `dev_dependencies:` (2-space indent). static bool _hasMagicDevtoolsDep() { final pubspec = File(pubspecPathResolver()); if (!pubspec.existsSync()) return false; diff --git a/test/src/commands/dusk_install_command_test.dart b/test/src/commands/dusk_install_command_test.dart index c38b196..d83f5cd 100644 --- a/test/src/commands/dusk_install_command_test.dart +++ b/test/src/commands/dusk_install_command_test.dart @@ -562,6 +562,61 @@ class MyApp extends StatelessWidget { }, ); + // ------------------------------------------------------------------ + // magic_devtools dep + NO Magic.init: must NOT inject import or call + // ------------------------------------------------------------------ + + test( + 'magic_devtools dep without Magic.init: does NOT inject ' + "import 'package:magic_devtools/dusk.dart' or MagicDuskIntegration", + () async { + // Vanilla app: has magic_devtools in pubspec but NO `await Magic.init(` + // in main.dart. The import and install call must stay absent so + // `dart analyze` in the consumer does not trip over an unused import. + final mainDartPath = _seedProject( + tempDir, + pubspecDeps: const {'magic_devtools': 'any'}, + mainDartContents: ''' +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + @override + Widget build(BuildContext context) => const MaterialApp(home: SizedBox()); +} +''', + ); + DuskInstallCommand.mainDartPathResolver = () => mainDartPath; + DuskInstallCommand.pubspecPathResolver = + () => '${tempDir.path}/pubspec.yaml'; + + final exit = await DuskInstallCommand().handle(_ctx()); + expect(exit, equals(0)); + + final result = File(mainDartPath).readAsStringSync(); + expect( + result.contains("import 'package:magic_devtools/dusk.dart';"), + isFalse, + reason: + 'must NOT inject an unused magic_devtools import when main.dart ' + 'has no await Magic.init( anchor', + ); + expect( + result.contains('MagicDuskIntegration.install()'), + isFalse, + reason: + 'must NOT inject MagicDuskIntegration.install() when no Magic.init ' + 'anchor is present (container is never ready)', + ); + // The vanilla DuskPlugin wiring must still land. + expect(result.contains('DuskPlugin.install();'), isTrue); + }, + ); + test( 'chained Phase 2 skips artisan install when bin/dispatcher.dart ' 'already exists (idempotent re-run)', From 3d8fd3a5889862d803a19538420b90d8aa963886 Mon Sep 17 00:00:00 2001 From: Anilcan Cakir Date: Wed, 17 Jun 2026 15:23:24 +0300 Subject: [PATCH 3/3] release: 0.0.8 (magic_devtools install repoint + full doc/skill sync) - pubspec 0.0.7 -> 0.0.8 (0.0.7 is published; this ships the dusk:install repoint to package:magic_devtools/dusk.dart + the hasMagicInit gate). - example/pubspec path-dep pin -> 0.0.8. - CHANGELOG: promote [Unreleased] (install repoint + hasMagicInit fix) to [0.0.8] - 2026-06-17 + Documentation entry. - doc/skill sync to the magic_devtools extraction: installation.md pin ^0.0.2 -> ^0.0.8, SKILL.md version+stamp -> 0.0.8, cli-commands.md gate magic -> magic_devtools, magic-integration.md import + dev_dependency note, ARCHITECTURE.md magic_devtools attribution. --- ARCHITECTURE.md | 2 +- CHANGELOG.md | 16 +++++++++++++--- doc/commands/dusk-install.md | 17 ++++++++++------- doc/getting-started/installation.md | 10 ++++++---- doc/plugins/magic-integration.md | 14 +++++++++++++- example/pubspec.lock | 2 +- example/pubspec.yaml | 1 - lib/src/commands/dusk_install_command.dart | 10 +++++----- pubspec.yaml | 4 ++-- skills/fluttersdk-dusk/SKILL.md | 8 ++++---- .../fluttersdk-dusk/references/cli-commands.md | 4 +++- .../src/commands/dusk_install_command_test.dart | 2 +- 12 files changed, 59 insertions(+), 31 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index d7c461b..0253cc3 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -98,7 +98,7 @@ Every registration routes through `registerExtensionIdempotent` (from `fluttersd These cannot change without a coordinated bump across `magic` + `wind` + `dusk`: 1. `DuskSnapshotEnricher` typedef shape: `String? Function(Element, RefRegistry)` -2. `DuskPlugin.install()`, the `DuskPlugin.enrichers` live-append list (magic appends to it via `MagicDuskIntegration`), and `DuskPlugin.registerNavigateAdapter()` signatures +2. `DuskPlugin.install()`, the `DuskPlugin.enrichers` live-append list (`magic_devtools` appends to it via `MagicDuskIntegration`), and `DuskPlugin.registerNavigateAdapter()` signatures 3. `RefRegistry` public method signatures (`register`, `lookup`, `registerQuery`, `lookupQuery`, `disposeAll`, `resetForTesting`) 4. The 6 alpha-1 MCP tool names (`dusk_snap`, `dusk_tap`, `dusk_screenshot`, `dusk_hover`, `dusk_drag`, `dusk_type`) and their `ext.dusk.*` extension method names 5. `DuskActionabilityException` `reason` substring vocabulary (`not enabled`, `zero rect`, `off-viewport`, `not stable`, `obscured by`) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58ab925..79e9baf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,14 +8,22 @@ This project follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0. ## [Unreleased] -### Fixed +--- -- **`dusk:install` no longer injects `import 'package:magic_devtools/dusk.dart';` or `MagicDuskIntegration.install()` into a vanilla Flutter app that has `magic_devtools` in its pubspec but no `await Magic.init(` call in `lib/main.dart`.** Previously, the `magic_devtools` wiring block ran whenever the pubspec listed the dependency, regardless of whether a `Magic.init` anchor existed. This left an unused import in the consumer's file, causing `dart analyze` to fail. The gate is now `hasMagicInit && _hasMagicDevtoolsDep()`, matching the block's own intent documented in the comment above it. The existing `try/catch` around `injectAfterMagicInit` is retained as a defensive fallback. +## [0.0.8] - 2026-06-17 ### Changed - **`dusk:install` now injects `import 'package:magic_devtools/dusk.dart';` and gates on the `magic_devtools` dependency** instead of the removed `package:magic/dusk_integration.dart`. The `MagicDuskIntegration` class was extracted from the `magic` core into the new `magic_devtools` package; the injected class name (`MagicDuskIntegration.install()`) is unchanged. Consumers that follow magic's install.yaml (which adds `magic_devtools` to dev_dependencies before running `dusk:install`) get the integration wired automatically; magic-only consumers without `magic_devtools` in pubspec.yaml are unaffected. Coordinated with the magic_devtools extraction. +### Fixed + +- **`dusk:install` no longer injects `import 'package:magic_devtools/dusk.dart';` or `MagicDuskIntegration.install()` into a vanilla Flutter app that has `magic_devtools` in its pubspec but no `await Magic.init(` call in `lib/main.dart`.** Previously, the `magic_devtools` wiring block ran whenever the pubspec listed the dependency, regardless of whether a `Magic.init` anchor existed. This left an unused import in the consumer's file, causing `dart analyze` to fail. The gate is now `hasMagicInit && _hasMagicDevtoolsDep()`, matching the block's own intent documented in the comment above it. The existing `try/catch` around `injectAfterMagicInit` is retained as a defensive fallback. + +### Documentation + +- Docs, skill, and example synced to the `magic_devtools` extraction: `doc/plugins/magic-integration.md` updated to note that `MagicDuskIntegration` now ships in `magic_devtools` (add as a dev_dependency) and shows the required `import 'package:magic_devtools/dusk.dart';`. `skills/fluttersdk-dusk/references/cli-commands.md` updated to reflect the `magic_devtools` gate and `magic_devtools/dusk.dart` import. `ARCHITECTURE.md` frozen-contracts item updated from `magic` to `magic_devtools`. Version pins bumped to `^0.0.8` throughout (`pubspec.yaml`, `example/pubspec.yaml`, `doc/getting-started/installation.md`, `skills/fluttersdk-dusk/SKILL.md`). + --- ## [0.0.7] - 2026-06-17 @@ -203,7 +211,9 @@ Initial public release of `fluttersdk_dusk`. E2E driver for Flutter apps. Snapsh `DuskSnapshotEnricher` typedef, `DuskPlugin.install` / `DuskPlugin.enrichers` / `DuskPlugin.registerNavigateAdapter`, `RefRegistry` public methods (`register`, `lookup`, `registerQuery`, `lookupQuery`, `disposeAll`, `resetForTesting`), and every MCP tool name / `ext.dusk.*` extension name are part of the public 0.0.1 contract. Future releases keep these stable across the 0.x line; any change requires a coordinated bump with `magic` + `wind`. -[Unreleased]: https://github.com/fluttersdk/dusk/compare/0.0.6...HEAD +[Unreleased]: https://github.com/fluttersdk/dusk/compare/0.0.8...HEAD +[0.0.8]: https://github.com/fluttersdk/dusk/compare/0.0.7...0.0.8 +[0.0.7]: https://github.com/fluttersdk/dusk/compare/0.0.6...0.0.7 [0.0.6]: https://github.com/fluttersdk/dusk/compare/0.0.5...0.0.6 [0.0.5]: https://github.com/fluttersdk/dusk/compare/0.0.4...0.0.5 [0.0.4]: https://github.com/fluttersdk/dusk/compare/0.0.3...0.0.4 diff --git a/doc/commands/dusk-install.md b/doc/commands/dusk-install.md index 4fbf714..f772e05 100644 --- a/doc/commands/dusk-install.md +++ b/doc/commands/dusk-install.md @@ -2,7 +2,7 @@ One-shot bootstrap for `fluttersdk_dusk`. Runs in two phases, both idempotent: -1. **Patch `lib/main.dart`** — inject the imports, `WidgetsFlutterBinding.ensureInitialized()`, and a `kDebugMode`-gated `DuskPlugin.install()` block before `runApp(`. Magic-stack apps additionally wire `MagicDuskIntegration.install()` after `Magic.init(`. +1. **Patch `lib/main.dart`** — inject the imports (including `import 'package:magic_devtools/dusk.dart';` when applicable), `WidgetsFlutterBinding.ensureInitialized()`, and a `kDebugMode`-gated `DuskPlugin.install()` block before `runApp(`. Magic-stack apps additionally wire `MagicDuskIntegration.install()` after `Magic.init(` when `magic_devtools:` is present in `pubspec.yaml`. 2. **Chain fastcli setup** — best-effort `dart run fluttersdk_dusk install` (scaffolds `bin/dispatcher.dart` + `./bin/fsa` AOT wrapper) + `dart run fluttersdk_dusk plugin:install fluttersdk_dusk` (registers `DuskArtisanProvider`). Both sub-process calls are skipped when their idempotency markers already exist (`bin/dispatcher.dart` for the scaffold, `.artisan/installed/fluttersdk_dusk.json` for the plugin record). Failures are swallowed with a warning; the Phase 1 patch always succeeds on its own, so the consumer can still drive dusk via `dart run fluttersdk_dusk ` even when the chain skipped. Together, the two phases mean a fresh consumer needs only: @@ -12,7 +12,7 @@ flutter pub add fluttersdk_dusk dart run fluttersdk_dusk dusk:install ``` -After the second command, `./bin/fsa list` surfaces all 32 `dusk:*` commands and `./bin/fsa mcp:serve` exposes the 31 MCP tools. +After the second command, `./bin/fsa list` surfaces all 34 `dusk:*` commands and `./bin/fsa mcp:serve` exposes the 33 MCP tools. --- @@ -46,7 +46,7 @@ dart run fluttersdk_dusk dusk:install | Input | Source | Purpose | |-------|--------|---------| | `lib/main.dart` path | `DuskInstallCommand.mainDartPathResolver()` (defaults to `lib/main.dart`) | Target file for snippet injection. Test seam: override the resolver to point at a fixture. | -| `pubspec.yaml` path | `DuskInstallCommand.pubspecPathResolver()` (defaults to `pubspec.yaml`) | Inspected for `magic:` and `fluttersdk_wind:` dependency entries. Drives the conditional wiring decisions described under [Anchor modes](#anchor-modes). | +| `pubspec.yaml` path | `DuskInstallCommand.pubspecPathResolver()` (defaults to `pubspec.yaml`) | Inspected for `magic_devtools:` and `fluttersdk_wind:` dependency entries. Drives the conditional wiring decisions described under [Anchor modes](#anchor-modes). | Both resolvers are public static fields so tests can override per-test without leaking files into the running test process' cwd. @@ -71,7 +71,7 @@ No structured payload is emitted. Status flows through `ArtisanOutput.info` / `s The injector picks one of two anchor strings depending on what `lib/main.dart` already contains: -- **Magic-stack apps** (`lib/main.dart` contains `await Magic.init(`): `DuskPlugin.install()` is wired BEFORE `Magic.init(` so the driver is live during Magic boot. When `magic:` is also a pubspec dependency, `MagicDuskIntegration.install()` is also injected AFTER `Magic.init()` (the integration queries `Magic.find()` for the form and nav enrichers, which only resolves once the container is ready). +- **Magic-stack apps** (`lib/main.dart` contains `await Magic.init(`): `DuskPlugin.install()` is wired BEFORE `Magic.init(` so the driver is live during Magic boot. When `magic_devtools:` is also a pubspec dependency or dev_dependency, `import 'package:magic_devtools/dusk.dart';` is added and `MagicDuskIntegration.install()` is injected AFTER `Magic.init()` (the integration queries `Magic.find()` for the form and nav enrichers, which only resolves once the container is ready). - **Vanilla apps** (no `Magic.init` anchor): `DuskPlugin.install()` is wired immediately before `runApp(`. When the consumer's pubspec lists `fluttersdk_wind:` as a top-level dependency, `Wind.installDebugResolver()` lands inside the same `kDebugMode` block as `DuskPlugin.install()`. Wind alpha-10 no longer ships a dusk-specific integration class; dusk reads wind state through the neutral `WindDebugRegistry` bridge at snap time. The Wind enricher wiring is independent of the Magic detection: a magic-free app with `fluttersdk_wind` still gets the wind metadata block. @@ -80,7 +80,7 @@ The full sub-step list (from the source docblock): 1. Add the two required imports (`kDebugMode` from `package:flutter/foundation.dart`; the `package:fluttersdk_dusk/dusk.dart` barrel). 2. Inject `WidgetsFlutterBinding.ensureInitialized()` (skip when already present) plus the `kDebugMode`-gated dusk block before the canonical install anchor. -3. When pubspec has `magic:` AND main.dart has `await Magic.init(`, inject `MagicDuskIntegration.install()` AFTER that call. +3. When pubspec has `magic_devtools:` (dependency or dev_dependency) AND main.dart has `await Magic.init(`, inject `import 'package:magic_devtools/dusk.dart';` and `MagicDuskIntegration.install()` AFTER that call. --- @@ -127,9 +127,12 @@ The injector early-returns on every duplicate snippet. The output still prints ` ### 3. Magic-stack app with wind enricher -When pubspec lists both `magic:` and `fluttersdk_wind:`, the post-install `lib/main.dart` looks like: +When pubspec lists both `magic_devtools:` and `fluttersdk_wind:`, the post-install `lib/main.dart` looks like: ```dart +import 'package:fluttersdk_dusk/dusk.dart'; +import 'package:magic_devtools/dusk.dart'; + void main() async { WidgetsFlutterBinding.ensureInitialized(); if (kDebugMode) { @@ -138,7 +141,7 @@ void main() async { } await Magic.init(MyApp.new); if (kDebugMode) { - MagicDuskIntegration.install(); + MagicDuskIntegration.install(); // magic_devtools wiring } } ``` diff --git a/doc/getting-started/installation.md b/doc/getting-started/installation.md index e86da70..5484b53 100644 --- a/doc/getting-started/installation.md +++ b/doc/getting-started/installation.md @@ -44,7 +44,7 @@ Alternatively, add it manually to `pubspec.yaml`: ```yaml dependencies: - fluttersdk_dusk: ^0.0.2 + fluttersdk_dusk: ^0.0.8 ``` Then fetch dependencies: @@ -94,13 +94,15 @@ either, both, or neither depending on your stack. | Integration | Package | Enrichment | |:------------|:--------|:-----------| -| `MagicDuskIntegration.install()` | `magic` | MagicForm field values, validation state, named route per node. | +| `MagicDuskIntegration.install()` | `magic_devtools` | MagicForm field values, validation state, named route per node. | | `Wind.installDebugResolver()` (in `package:fluttersdk_wind/fluttersdk_wind.dart`) | `fluttersdk_wind` >= alpha-10 | Wind state surfaces through the neutral `WindDebugRegistry` bridge; dusk emits the 6 core fields (breakpoint, brightness, platform, states, bgColor, textColor) automatically without enricher registration. | ```dart +import 'package:magic_devtools/dusk.dart'; // magic_devtools only +// ... if (kDebugMode) { DuskPlugin.install(); - MagicDuskIntegration.install(); // magic-stack only + MagicDuskIntegration.install(); // magic-stack only (from magic_devtools) Wind.installDebugResolver(); // wind UI only (alpha-10+) } ``` @@ -127,7 +129,7 @@ The optional `mcp:install` step in the next section writes the plugin-aware `.mc ## Wire MCP tools -With artisan registered, expose dusk's 31 MCP tools to your AI client by writing the `.mcp.json` entry: +With artisan registered, expose dusk's 33 MCP tools to your AI client by writing the `.mcp.json` entry: ```bash dart run fluttersdk_dusk mcp:install diff --git a/doc/plugins/magic-integration.md b/doc/plugins/magic-integration.md index 80da728..9bd958a 100644 --- a/doc/plugins/magic-integration.md +++ b/doc/plugins/magic-integration.md @@ -7,6 +7,15 @@ of `DuskSnapshotEnricher` callbacks against `DuskPlugin.enrichers`, so every `dusk:snap` (or `dusk_snap` MCP) output carries Magic-aware annotations alongside the standard Semantics tree. +`MagicDuskIntegration` ships in the **`magic_devtools`** package, not in magic +core. Add it as a dev_dependency (it is debug-only; release builds tree-shake +the `kDebugMode` branch that calls `install()`): + +```yaml +dev_dependencies: + magic_devtools: ^ +``` + This document covers the five behavioural enrichers most likely to drive an agent's reasoning loop. The full integration ships fourteen enrichers; the nine not covered here (`magicFormEnricher`, `magicNavigationEnricher`, @@ -27,9 +36,12 @@ require the service providers to be live). It must also run **after** `DuskPlugin.install()`, because the `DuskPlugin.enrichers` list is the target the integration mutates. -The canonical debug-only host integration: +The canonical debug-only host integration (`MagicDuskIntegration` comes from +the `magic_devtools` dev_dependency): ```dart +import 'package:magic_devtools/dusk.dart'; // from magic_devtools dev_dependency + void main() async { WidgetsFlutterBinding.ensureInitialized(); await Magic.init(); diff --git a/example/pubspec.lock b/example/pubspec.lock index baf9407..1b18998 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -129,7 +129,7 @@ packages: path: ".." relative: true source: path - version: "0.0.7" + version: "0.0.8" fluttersdk_wind_diagnostics_contracts: dependency: transitive description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 9a326ac..41918c0 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -37,7 +37,6 @@ dependencies: fluttersdk_dusk: path: .. - version: 0.0.7 dev_dependencies: flutter_test: diff --git a/lib/src/commands/dusk_install_command.dart b/lib/src/commands/dusk_install_command.dart index e0bca66..bad83aa 100644 --- a/lib/src/commands/dusk_install_command.dart +++ b/lib/src/commands/dusk_install_command.dart @@ -16,10 +16,10 @@ import 'package:fluttersdk_artisan/artisan.dart'; /// Two anchor modes: /// - Magic-stack apps (`lib/main.dart` contains `await Magic.init(`): /// wire `DuskPlugin.install()` BEFORE `Magic.init` so the driver is -/// live during Magic boot. When `magic_devtools:` is also a pubspec dep, ALSO -/// inject `MagicDuskIntegration.install()` AFTER `Magic.init` (the -/// integration queries `Magic.find()` for the form / nav -/// enrichers, which only resolves once the container is ready). +/// live during Magic boot. When `magic_devtools:` is also a pubspec +/// dependency or dev_dependency, ALSO inject `MagicDuskIntegration.install()` +/// AFTER `Magic.init` (the integration queries `Magic.find()` for the +/// form / nav enrichers, which only resolves once the container is ready). /// - Vanilla apps: wire `DuskPlugin.install()` before `runApp(`. /// /// Idempotent across re-runs: each `addImport` / `injectBeforeAnchor` / @@ -233,7 +233,7 @@ class DuskInstallCommand extends ArtisanCommand { FileHelper.writeFile(mainDartPath, source); } - // 3. Magic-side coordinated wiring when the consumer pulls in magic. + // 3. Magic-side coordinated wiring when the consumer pulls in magic_devtools. // Detect via pubspec.yaml; skip silently when magic_devtools is not a dep // or when main.dart has no Magic.init() anchor (vanilla app). if (hasMagicInit && _hasMagicDevtoolsDep()) { diff --git a/pubspec.yaml b/pubspec.yaml index 5276710..7dfb1c0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: fluttersdk_dusk description: "Flutter E2E driver for LLM agents and CI. 34 CLI commands and 33 MCP tools drive a running app over VM Service extensions; no flutter_test harness needed." -version: 0.0.7 +version: 0.0.8 homepage: https://fluttersdk.com/dusk repository: https://github.com/fluttersdk/dusk issue_tracker: https://github.com/fluttersdk/dusk/issues @@ -32,7 +32,7 @@ dependencies: executables: # `dart run fluttersdk_dusk ` is the dusk-flavoured artisan wrapper. - # Surfaces 32 CLI commands + 31 MCP tools (28 ext.dusk.* + 3 artisan:dusk:*) via DuskArtisanProvider. + # Surfaces 34 CLI commands + 33 MCP tools (30 ext.dusk.* + 3 artisan:dusk:*) via DuskArtisanProvider. fluttersdk_dusk: fluttersdk_dusk dev_dependencies: diff --git a/skills/fluttersdk-dusk/SKILL.md b/skills/fluttersdk-dusk/SKILL.md index b01492e..146c188 100644 --- a/skills/fluttersdk-dusk/SKILL.md +++ b/skills/fluttersdk-dusk/SKILL.md @@ -1,11 +1,11 @@ --- name: fluttersdk-dusk -description: "fluttersdk_dusk: E2E driver for Flutter apps that lets an LLM agent see (snap, observe, screenshot) and act (tap, type, drag, scroll, navigate) on a running Flutter app via 31 MCP tools (`dusk_*`) and 32 matching CLI commands (`./bin/fsa dusk:*`). Snapshots emit a YAML Semantics tree with stable `[ref=eN]` tokens; `dusk_find` and `dusk_observe` mint re-resolvable `q` query handles. Every gesture passes a 6-step actionability gate with substring-parseable failure reasons (`not enabled`, `zero rect`, `off-viewport`, `not stable`, `obscured by`, `defunct`). TRIGGER when: any `dusk_*` MCP tool call, any `dusk:*` CLI command, `./bin/fsa` invocation, the user asks the agent to drive / inspect / test / debug a running Flutter app, the user mentions snap / observe / actionability / ref / eN / qN, or the conversation touches end-to-end testing of a Flutter UI. DO NOT TRIGGER when: only authoring `flutter_test` widget tests, only reading telescope ring buffers without driving the UI (use fluttersdk-telescope), or only modifying Dart source without running it." -version: 0.0.3 +description: "fluttersdk_dusk: E2E driver for Flutter apps that lets an LLM agent see (snap, observe, screenshot) and act (tap, type, drag, scroll, navigate) on a running Flutter app via 33 MCP tools (`dusk_*`) and 34 matching CLI commands (`./bin/fsa dusk:*`). Snapshots emit a YAML Semantics tree with stable `[ref=eN]` tokens; `dusk_find` and `dusk_observe` mint re-resolvable `q` query handles. Every gesture passes a 6-step actionability gate with substring-parseable failure reasons (`not enabled`, `zero rect`, `off-viewport`, `not stable`, `obscured by`, `defunct`). TRIGGER when: any `dusk_*` MCP tool call, any `dusk:*` CLI command, `./bin/fsa` invocation, the user asks the agent to drive / inspect / test / debug a running Flutter app, the user mentions snap / observe / actionability / ref / eN / qN, or the conversation touches end-to-end testing of a Flutter UI. DO NOT TRIGGER when: only authoring `flutter_test` widget tests, only reading telescope ring buffers without driving the UI (use fluttersdk-telescope), or only modifying Dart source without running it." +version: 0.0.8 when_to_use: "Any task where the agent drives or inspects a running Flutter app via dusk: calling `dusk_*` MCP tools in a loop (snap, tap, type, screenshot, hot_reload_and_snap), invoking `./bin/fsa dusk:` from a shell, recovering from an actionability failure, choosing between `e` and `q` ref tokens, waiting for text or network idle, navigating routes, or filling a form." --- - + # fluttersdk_dusk @@ -100,7 +100,7 @@ and verify with `./bin/fsa dusk:doctor`. dusk-aware Dart REPL lives behind `./bin/fsa tinker` (one-shot form: `./bin/fsa tinker --eval=""`). -## 2. Tool surface (31 MCP tools, 32 CLI commands) +## 2. Tool surface (33 MCP tools, 34 CLI commands) | Family | Tools | Mental model | |---|---|---| diff --git a/skills/fluttersdk-dusk/references/cli-commands.md b/skills/fluttersdk-dusk/references/cli-commands.md index c493bac..0f1c22d 100644 --- a/skills/fluttersdk-dusk/references/cli-commands.md +++ b/skills/fluttersdk-dusk/references/cli-commands.md @@ -202,7 +202,9 @@ dart run fluttersdk_dusk dusk:install # one-time setup `dusk:install` patches `lib/main.dart` (adds `kDebugMode` guard + `DuskPlugin.install()`), scaffolds `./bin/fsa`, registers the provider in -`lib/app/_plugins.g.dart`, and (when magic is in pubspec) injects +`lib/app/_plugins.g.dart`, and (when `magic_devtools` is in pubspec AND +`lib/main.dart` contains an `await Magic.init(` anchor) injects +`import 'package:magic_devtools/dusk.dart';` and `MagicDuskIntegration.install()` after `Magic.init()`. Idempotent. `dusk:doctor` checks: diff --git a/test/src/commands/dusk_install_command_test.dart b/test/src/commands/dusk_install_command_test.dart index d83f5cd..ebfacde 100644 --- a/test/src/commands/dusk_install_command_test.dart +++ b/test/src/commands/dusk_install_command_test.dart @@ -603,7 +603,7 @@ class MyApp extends StatelessWidget { isFalse, reason: 'must NOT inject an unused magic_devtools import when main.dart ' - 'has no await Magic.init( anchor', + 'has no `await Magic.init(` anchor', ); expect( result.contains('MagicDuskIntegration.install()'),