feat(unified_analytics): add dependency telemetry and SDK constraint to Event.dartCliCommandExecuted
diff --git a/.github/workflows/unified_analytics.yaml b/.github/workflows/unified_analytics.yaml
index 9a9b7f5..f98eac2 100644
--- a/.github/workflows/unified_analytics.yaml
+++ b/.github/workflows/unified_analytics.yaml
@@ -27,6 +27,8 @@
         include:
           - sdk: stable
             run-tests: true
+          - sdk: dev
+            check-formatting: true
     steps:
       - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
       - uses: dart-lang/setup-dart@65eb853c7ba17dde3be364c3d2858773e7144260
@@ -38,7 +40,7 @@
       - run: dart analyze --fatal-infos
 
       - run: dart format --output=none --set-exit-if-changed .
-        if: ${{matrix.run-tests}}
+        if: ${{matrix.check-formatting}}
 
       - run: dart test
         if: ${{matrix.run-tests}}
diff --git a/pkgs/unified_analytics/CHANGELOG.md b/pkgs/unified_analytics/CHANGELOG.md
index 1ffb12c..f0455f4 100644
--- a/pkgs/unified_analytics/CHANGELOG.md
+++ b/pkgs/unified_analytics/CHANGELOG.md
@@ -1,3 +1,11 @@
+## 8.0.16
+
+- Added optional `pubspecHasFlutterSdk` and `pubspecDependencies` parameters to
+  the `Event.dartCliCommandExecuted` constructor.
+- Dependencies are deterministically sorted and chunked using a hash-based
+  algorithm to fit within Google Analytics 4 parameter limitations without
+  alphabetical bias.
+
 ## 8.0.15
 - Added IDE and plugin information to `Event.serverSession`.
 - Discard any `Exception` or `Error` thrown while reading or writing analytics logs.
@@ -11,7 +19,7 @@
 
 ## 8.0.12
 - Require Dart 3.10
-- Added `success` indicator and `label` to `Event.flutterTrackAndroidDependencies` 
+- Added `success` indicator and `label` to `Event.flutterTrackAndroidDependencies`
 
 ## 8.0.11
 - Added `Event.flutterTrackAndroidDependencies` to track android dependencies.
diff --git a/pkgs/unified_analytics/lib/src/constants.dart b/pkgs/unified_analytics/lib/src/constants.dart
index c9f5fb6..5cc12b8 100644
--- a/pkgs/unified_analytics/lib/src/constants.dart
+++ b/pkgs/unified_analytics/lib/src/constants.dart
@@ -87,7 +87,7 @@
 const String kLogFileName = 'dart-flutter-telemetry.log';
 
 /// The current version of the package, should be in line with pubspec version.
-const String kPackageVersion = '8.0.15';
+const String kPackageVersion = '8.0.16';
 
 /// The minimum length for a session.
 const int kSessionDurationMinutes = 30;
diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart
index 326f540..516172b 100644
--- a/pkgs/unified_analytics/lib/src/event.dart
+++ b/pkgs/unified_analytics/lib/src/event.dart
@@ -4,6 +4,8 @@
 
 import 'dart:convert';
 
+import 'package:meta/meta.dart';
+
 import 'enums.dart';
 
 final class Event {
@@ -462,12 +464,19 @@
     required String name,
     required String enabledExperiments,
     int? exitCode,
+    bool? pubspecHasFlutterSdk,
+    Set<String>? pubspecDependencies,
+    String? pubspecEnvironmentSdk,
   }) : this._(
          eventName: DashEvent.dartCliCommandExecuted,
          eventData: {
            'name': name,
            'enabledExperiments': enabledExperiments,
            'exitCode': ?exitCode,
+           'pubspec_has_flutter_sdk': ?pubspecHasFlutterSdk,
+           'pubspec_environment_sdk': ?pubspecEnvironmentSdk,
+           if (pubspecDependencies != null)
+             ...chunkDependencies(pubspecDependencies),
          },
        );
 
@@ -1182,3 +1191,90 @@
   /// This must be a JSON-encodable [Map].
   Map<String, Object> toMap();
 }
+
+/// Public helper in unified_analytics to sort and chunk dependencies.
+///
+/// Respects GA4's 100-character limit per parameter and 25-parameter limit
+/// per event.
+///
+/// To eliminate alphabetical bias (e.g., always omitting packages starting
+/// with 'z' when exceeding the 20-chunk cap), dependencies are sorted
+/// deterministically by their FNV-1a hash value instead of alphabetically.
+/// This provides a statistically unbiased, pseudo-random sample of packages
+/// for large projects while remaining 100% stable, reproducible, and testable
+/// across runs.
+@visibleForTesting
+Map<String, String> chunkDependencies(Set<String> deps) {
+  if (deps.isEmpty) return const {};
+
+  // Sort deterministically by FNV-1a hash value instead of alphabetically
+  // to eliminate systemic alphabetical bias during truncation. Fall back to
+  // alphabetical comparison if there is a hash collision to guarantee absolute
+  // determinism.
+  final sortedDeps = deps.toList()
+    ..sort((a, b) {
+      final hashA = _fnv1a(a);
+      final hashB = _fnv1a(b);
+      if (hashA != hashB) {
+        return hashA.compareTo(hashB);
+      }
+      return a.compareTo(b);
+    });
+
+  final chunks = <String, String>{};
+  var currentChunk = <String>[];
+  var currentLength = 0;
+  var chunkIndex = 0;
+
+  // We have a maximum of 25 parameters per event in GA4. Standard event
+  // parameters (name, enabledExperiments, exitCode, pubspec_has_flutter_sdk)
+  // take up to 4 slots, leaving 21 slots. Capping at 20 chunks guarantees
+  // safety.
+  const maxChunks = 20;
+
+  for (final dep in sortedDeps) {
+    // Guard: Skip package names that are somehow longer than 100 characters
+    // to prevent violating GA4's value length limit.
+    if (dep.length > 100) continue;
+
+    final lengthToAdd = dep.length + (currentChunk.isEmpty ? 0 : 1);
+    if (currentLength + lengthToAdd > 100) {
+      chunks['pubspec_dep_$chunkIndex'] = currentChunk.join(',');
+      chunkIndex++;
+
+      // Stop adding chunks if we reach the GA4 parameter count safety limit
+      if (chunkIndex >= maxChunks) {
+        currentChunk = const [];
+        break;
+      }
+
+      currentChunk = [dep];
+      currentLength = dep.length;
+    } else {
+      currentChunk.add(dep);
+      currentLength += lengthToAdd;
+    }
+  }
+
+  if (currentChunk.isNotEmpty && chunkIndex < maxChunks) {
+    chunks['pubspec_dep_$chunkIndex'] = currentChunk.join(',');
+  }
+
+  return chunks;
+}
+
+/// Computes a deterministic 32-bit FNV-1a hash of a string.
+///
+/// This is used to shuffle dependency names in a stable, pseudo-random
+/// way to eliminate alphabetical bias when sampling packages for telemetry
+/// while maintaining 100% determinism across runs.
+int _fnv1a(String s) {
+  var hash = 2166136261;
+  for (var i = 0; i < s.length; i++) {
+    hash ^= s.codeUnitAt(i);
+    // Split the multiplication to prevent exceeding the 53-bit safe integer
+    // limit on the web.
+    hash = (((hash & 0xff) << 24) + (hash * 403)) & 0xffffffff;
+  }
+  return hash;
+}
diff --git a/pkgs/unified_analytics/pubspec.yaml b/pkgs/unified_analytics/pubspec.yaml
index 5b78a0b..60bf37a 100644
--- a/pkgs/unified_analytics/pubspec.yaml
+++ b/pkgs/unified_analytics/pubspec.yaml
@@ -5,7 +5,7 @@
 # LINT.IfChange
 # When updating this, keep the version consistent with the changelog and the
 # value in lib/src/constants.dart.
-version: 8.0.15
+version: 8.0.16
 # LINT.ThenChange(lib/src/constants.dart)
 repository: https://github.com/dart-lang/tools/tree/main/pkgs/unified_analytics
 issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aunified_analytics
diff --git a/pkgs/unified_analytics/test/event_test.dart b/pkgs/unified_analytics/test/event_test.dart
index 7dcc27e..0d162de 100644
--- a/pkgs/unified_analytics/test/event_test.dart
+++ b/pkgs/unified_analytics/test/event_test.dart
@@ -7,7 +7,6 @@
 import 'package:test/test.dart';
 import 'package:unified_analytics/src/enums.dart';
 import 'package:unified_analytics/src/event.dart';
-import 'package:unified_analytics/unified_analytics.dart';
 
 void main() {
   test('Event.analysisStatistics constructed', () {
@@ -198,6 +197,130 @@
     expect(constructedEvent.eventData.length, 3);
   });
 
+  test('Event.dartCliCommandExecuted constructed with Set of dependencies', () {
+    final deps = {'path', 'meta', 'collection'};
+    Event generateEvent() => Event.dartCliCommandExecuted(
+      name: 'name',
+      enabledExperiments: 'enabledExperiments',
+      exitCode: 0,
+      pubspecHasFlutterSdk: true,
+      pubspecDependencies: deps,
+      pubspecEnvironmentSdk: '^3.0.0',
+    );
+
+    final constructedEvent = generateEvent();
+
+    expect(generateEvent, returnsNormally);
+    expect(constructedEvent.eventName, DashEvent.dartCliCommandExecuted);
+    expect(constructedEvent.eventData['name'], 'name');
+    expect(
+      constructedEvent.eventData['enabledExperiments'],
+      'enabledExperiments',
+    );
+    expect(constructedEvent.eventData['exitCode'], 0);
+    expect(constructedEvent.eventData['pubspec_has_flutter_sdk'], true);
+    expect(constructedEvent.eventData['pubspec_environment_sdk'], '^3.0.0');
+
+    // Delegates chunking to chunkDependencies
+    final expectedChunks = chunkDependencies(deps);
+    expect(
+      constructedEvent.eventData['pubspec_dep_0'],
+      expectedChunks['pubspec_dep_0'],
+    );
+    expect(constructedEvent.eventData.length, 6);
+  });
+
+  test('Event.dartCliCommandExecuted delegates to chunkDependencies', () {
+    final deps = <String>{};
+    for (var i = 1; i <= 50; i++) {
+      deps.add('dep_$i');
+    }
+
+    final event = Event.dartCliCommandExecuted(
+      name: 'name',
+      enabledExperiments: 'enabledExperiments',
+      pubspecDependencies: deps,
+    );
+
+    final expected = chunkDependencies(deps);
+    for (final entry in expected.entries) {
+      expect(event.eventData[entry.key], entry.value);
+    }
+  });
+
+  group('chunkDependencies', () {
+    test('empty set returns empty map', () {
+      expect(chunkDependencies({}), isEmpty);
+    });
+
+    test('sorts deterministically using FNV-1a hash values', () {
+      final deps = {'path', 'meta', 'collection', 'args', 'yaml', 'http'};
+
+      // Verification of determinism across multiple calls
+      final result1 = chunkDependencies(deps);
+      final result2 = chunkDependencies(deps);
+      expect(result1, result2);
+
+      // Verify that it is NOT sorted alphabetically.
+      // Alphabetical order would be: args, collection, http, meta, path, yaml
+      final alphabeticalList = deps.toList()..sort();
+      final reportedList = result1['pubspec_dep_0']!.split(',');
+      expect(reportedList, isNot(alphabeticalList));
+    });
+
+    test('chunks correctly based on 100-character limit', () {
+      // 10 dependencies, each 16 characters.
+      // Delimited by ',' means 17 characters per dep (except last).
+      final deps = <String>{};
+      for (var i = 1; i <= 10; i++) {
+        deps.add('dep_${i.toString().padLeft(2, '0')}_123456789');
+      }
+
+      final result = chunkDependencies(deps);
+
+      // Since each dep is 16 chars, 6 deps would be 6 * 16 + 5 = 101 chars,
+      // which is > 100. So max 5 deps fit in a single chunk
+      // (5 * 16 + 4 = 84 chars). Therefore, it must be split across
+      // exactly 2 chunks.
+      expect(result.containsKey('pubspec_dep_0'), isTrue);
+      expect(result.containsKey('pubspec_dep_1'), isTrue);
+      expect(result.containsKey('pubspec_dep_2'), isFalse);
+
+      expect(result['pubspec_dep_0']!.length, lessThanOrEqualTo(100));
+      expect(result['pubspec_dep_1']!.length, lessThanOrEqualTo(100));
+
+      final allReported = <String>{
+        ...result['pubspec_dep_0']!.split(','),
+        ...result['pubspec_dep_1']!.split(','),
+      };
+      expect(allReported, deps);
+    });
+
+    test('caps at 20 chunks', () {
+      // Generate 150 dependencies, each 15 characters.
+      // This would require ~24 chunks, but must be capped at 20.
+      final deps = <String>{};
+      for (var i = 1; i <= 150; i++) {
+        deps.add('dep_${i.toString().padLeft(3, '0')}_12345678');
+      }
+
+      final result = chunkDependencies(deps);
+
+      expect(result.containsKey('pubspec_dep_0'), isTrue);
+      expect(result.containsKey('pubspec_dep_19'), isTrue);
+      expect(result.containsKey('pubspec_dep_20'), isFalse);
+      expect(result.length, 20);
+    });
+
+    test('guards against and skips package names > 100 characters', () {
+      final longName = 'a' * 101;
+      final result = chunkDependencies({longName, 'path'});
+
+      expect(result['pubspec_dep_0'], 'path');
+      expect(result.length, 1);
+    });
+  });
+
   test('Event.doctorValidatorResult constructed', () {
     Event generateEvent() => Event.doctorValidatorResult(
       validatorName: 'validatorName',