Fix names of package CI scripts (#1962)

Fix string_scanner, io, etc
Add a script to keep things up-to-date
diff --git a/.github/workflows/bazel_worker.yaml b/.github/workflows/bazel_worker.yaml
index b448219..4446793 100644
--- a/.github/workflows/bazel_worker.yaml
+++ b/.github/workflows/bazel_worker.yaml
@@ -5,12 +5,12 @@
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/bazel_worker.yml'
+      - '.github/workflows/bazel_worker.yaml'
       - 'pkgs/bazel_worker/**'
   pull_request:
     branches: [ main ]
     paths:
-      - '.github/workflows/bazel_worker.yml'
+      - '.github/workflows/bazel_worker.yaml'
       - 'pkgs/bazel_worker/**'
   schedule:
     - cron: "0 0 * * 0"
diff --git a/.github/workflows/benchmark_harness.yaml b/.github/workflows/benchmark_harness.yaml
index 8e0cc8a..04a5829 100644
--- a/.github/workflows/benchmark_harness.yaml
+++ b/.github/workflows/benchmark_harness.yaml
@@ -5,12 +5,12 @@
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/benchmark_harness.yml'
+      - '.github/workflows/benchmark_harness.yaml'
       - 'pkgs/benchmark_harness/**'
   pull_request:
     branches: [ main ]
     paths:
-      - '.github/workflows/benchmark_harness.yml'
+      - '.github/workflows/benchmark_harness.yaml'
       - 'pkgs/benchmark_harness/**'
   schedule:
     - cron: "0 0 * * 0"
diff --git a/.github/workflows/boolean_selector.yaml b/.github/workflows/boolean_selector.yaml
index 3e35050..4a74aed 100644
--- a/.github/workflows/boolean_selector.yaml
+++ b/.github/workflows/boolean_selector.yaml
@@ -5,12 +5,12 @@
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/boolean_selector.yml'
+      - '.github/workflows/boolean_selector.yaml'
       - 'pkgs/boolean_selector/**'
   pull_request:
     branches: [ main ]
     paths:
-      - '.github/workflows/boolean_selector.yml'
+      - '.github/workflows/boolean_selector.yaml'
       - 'pkgs/boolean_selector/**'
   schedule:
     - cron: "0 0 * * 0"
diff --git a/.github/workflows/browser_launcher.yaml b/.github/workflows/browser_launcher.yaml
index 41bf8da..699cac6 100644
--- a/.github/workflows/browser_launcher.yaml
+++ b/.github/workflows/browser_launcher.yaml
@@ -5,12 +5,12 @@
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/browser_launcher.yml'
+      - '.github/workflows/browser_launcher.yaml'
       - 'pkgs/browser_launcher/**'
   pull_request:
     branches: [ main ]
     paths:
-      - '.github/workflows/browser_launcher.yml'
+      - '.github/workflows/browser_launcher.yaml'
       - 'pkgs/browser_launcher/**'
   schedule:
     - cron: "0 0 * * 0"
diff --git a/.github/workflows/cli_config.yml b/.github/workflows/cli_config.yaml
similarity index 93%
rename from .github/workflows/cli_config.yml
rename to .github/workflows/cli_config.yaml
index 0ef1524..980df5c 100644
--- a/.github/workflows/cli_config.yml
+++ b/.github/workflows/cli_config.yaml
@@ -14,12 +14,13 @@
   schedule:
     - cron: "0 0 * * 0" # weekly
 
+defaults:
+  run:
+    working-directory: pkgs/cli_config/
+
 jobs:
   build_linux:
     runs-on: ubuntu-latest
-    defaults:
-      run:
-        working-directory: pkgs/cli_config
     strategy:
       matrix:
         sdk: [stable, dev] # {pkgs.versions}
@@ -58,9 +59,6 @@
 
   build_windows:
     runs-on: windows-latest
-    defaults:
-      run:
-        working-directory: pkgs/cli_config
     strategy:
       matrix:
         sdk: [stable, dev] # {pkgs.versions}
diff --git a/.github/workflows/cli_util.yaml b/.github/workflows/cli_util.yaml
index 1c9fec0..69aae5e 100644
--- a/.github/workflows/cli_util.yaml
+++ b/.github/workflows/cli_util.yaml
@@ -5,12 +5,12 @@
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/cli_util.yml'
+      - '.github/workflows/cli_util.yaml'
       - 'pkgs/cli_util/**'
   pull_request:
     branches: [ main ]
     paths:
-      - '.github/workflows/cli_util.yml'
+      - '.github/workflows/cli_util.yaml'
       - 'pkgs/cli_util/**'
   schedule:
     - cron: "0 0 * * 0"
diff --git a/.github/workflows/code_builder.yaml b/.github/workflows/code_builder.yaml
index 2b2ad16..a0d3ec2 100644
--- a/.github/workflows/code_builder.yaml
+++ b/.github/workflows/code_builder.yaml
@@ -5,12 +5,12 @@
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/code_builder.yml'
+      - '.github/workflows/code_builder.yaml'
       - 'pkgs/code_builder/**'
   pull_request:
     branches: [ main ]
     paths:
-      - '.github/workflows/code_builder.yml'
+      - '.github/workflows/code_builder.yaml'
       - 'pkgs/code_builder/**'
   schedule:
     - cron: "0 0 * * 0"
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yaml
similarity index 95%
rename from .github/workflows/coverage.yml
rename to .github/workflows/coverage.yaml
index ca4463f..2252b94 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yaml
@@ -1,16 +1,16 @@
-name: Dart CI
+name: package:coverage
 
 on:
   # Run on PRs and pushes to the default branch.
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/coverage.yml'
+      - '.github/workflows/coverage.yaml'
       - 'pkgs/coverage/**'
   pull_request:
     branches: [ main ]
     paths:
-      - '.github/workflows/coverage.yml'
+      - '.github/workflows/coverage.yaml'
       - 'pkgs/coverage/**'
   schedule:
     - cron: "0 0 * * 0"
diff --git a/.github/workflows/csslib.yaml b/.github/workflows/csslib.yaml
index c370293..3ce2cd9 100644
--- a/.github/workflows/csslib.yaml
+++ b/.github/workflows/csslib.yaml
@@ -5,12 +5,12 @@
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/csslib.yml'
+      - '.github/workflows/csslib.yaml'
       - 'pkgs/csslib/**'
   pull_request:
     branches: [ main ]
     paths:
-      - '.github/workflows/csslib.yml'
+      - '.github/workflows/csslib.yaml'
       - 'pkgs/csslib/**'
   schedule:
     - cron: "0 0 * * 0"
diff --git a/.github/workflows/extension_discovery.yml b/.github/workflows/extension_discovery.yaml
similarity index 90%
rename from .github/workflows/extension_discovery.yml
rename to .github/workflows/extension_discovery.yaml
index dd129a7..6a6d5d7 100644
--- a/.github/workflows/extension_discovery.yml
+++ b/.github/workflows/extension_discovery.yaml
@@ -4,12 +4,12 @@
 on:
   pull_request:
     paths:
-      - '.github/workflows/unified_analytics.yml'
+      - '.github/workflows/extension_discovery.yaml'
       - 'pkgs/extension_discovery/**'
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/extension_discovery.yml'
+      - '.github/workflows/extension_discovery.yaml'
       - 'pkgs/extension_discovery/**'
   schedule:
     - cron: '0 0 * * 0' # weekly
diff --git a/.github/workflows/file.yaml b/.github/workflows/file.yaml
new file mode 100644
index 0000000..3254f2e
--- /dev/null
+++ b/.github/workflows/file.yaml
@@ -0,0 +1,54 @@
+name: package:file
+permissions: read-all
+
+on:
+  # Run on PRs and pushes to the default branch.
+  push:
+    branches: [ main ]
+    paths:
+      - '.github/workflows/file.yaml'
+      - 'pkgs/file/**'
+      - 'pkgs/file_testing/**'
+  pull_request:
+    branches: [ main ]
+    paths:
+      - '.github/workflows/file.yaml'
+      - 'pkgs/file/**'
+      - 'pkgs/file_testing/**'
+  schedule:
+    - cron: "0 0 * * 0"
+
+defaults:
+  run:
+    working-directory: pkgs/file/
+
+jobs:
+  correctness:
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+      - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+        with:
+          sdk: dev
+
+      - run: dart pub get
+      - run: dart format --output=none --set-exit-if-changed .
+      - run: dart analyze --fatal-infos
+
+  test:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        package: [file]
+        os: [ubuntu-latest, macos-latest, windows-latest]
+        sdk: [stable, dev]
+
+    steps:
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+      - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+        with:
+          sdk: ${{ matrix.sdk }}
+
+      - run: dart pub get
+      - run: dart pub run test -j1
diff --git a/.github/workflows/file.yml b/.github/workflows/file.yml
deleted file mode 100644
index e8c3bff..0000000
--- a/.github/workflows/file.yml
+++ /dev/null
@@ -1,66 +0,0 @@
-name: package:file
-permissions: read-all
-
-on:
-  # Run on PRs and pushes to the default branch.
-  push:
-    branches: [ main ]
-    paths:
-      - '.github/workflows/file.yml'
-      - 'pkgs/file/**'
-      - 'pkgs/file_testing/**'
-  pull_request:
-    branches: [ main ]
-    paths:
-      - '.github/workflows/file.yml'
-      - 'pkgs/file/**'
-      - 'pkgs/file_testing/**'
-  schedule:
-    - cron: "0 0 * * 0"
-
-jobs:
-  correctness:
-    runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        package: [file, file_testing]
-
-    steps:
-      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
-      - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
-        with:
-          sdk: dev
-
-      - name: Install ${{ matrix.package }} dependencies
-        working-directory: pkgs/${{ matrix.package }}
-        run: dart pub get
-
-      - name: Verify formatting in ${{ matrix.package }}
-        working-directory: pkgs/${{ matrix.package }}
-        run: dart format --output=none --set-exit-if-changed .
-
-      - name: Analyze package ${{ matrix.package }} source
-        working-directory: pkgs/${{ matrix.package }}
-        run: dart analyze --fatal-infos
-
-  test:
-    runs-on: ${{ matrix.os }}
-    strategy:
-      matrix:
-        package: [file]
-        os: [ubuntu-latest, macos-latest, windows-latest]
-        sdk: [stable, dev]
-
-    steps:
-      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
-      - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
-        with:
-          sdk: ${{ matrix.sdk }}
-
-      - name: Install ${{ matrix.package }} dependencies
-        working-directory: pkgs/${{ matrix.package }}
-        run: dart pub get
-
-      - name: Run ${{ matrix.package }} Tests
-        working-directory: pkgs/${{ matrix.package }}
-        run: dart pub run test -j1
diff --git a/.github/workflows/file_testing.yaml b/.github/workflows/file_testing.yaml
new file mode 100644
index 0000000..f5e5a0e
--- /dev/null
+++ b/.github/workflows/file_testing.yaml
@@ -0,0 +1,36 @@
+name: package:file_testing
+permissions: read-all
+
+on:
+  # Run on PRs and pushes to the default branch.
+  push:
+    branches: [ main ]
+    paths:
+      - '.github/workflows/file_testing.yaml'
+      - 'pkgs/file/**'
+      - 'pkgs/file_testing/**'
+  pull_request:
+    branches: [ main ]
+    paths:
+      - '.github/workflows/file_testing.yaml'
+      - 'pkgs/file/**'
+      - 'pkgs/file_testing/**'
+  schedule:
+    - cron: "0 0 * * 0"
+
+defaults:
+  run:
+    working-directory: pkgs/file_testing/
+
+jobs:
+  correctness:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+      - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+        with:
+          sdk: dev
+
+      - run: dart pub get
+      - run: dart format --output=none --set-exit-if-changed .
+      - run: dart analyze --fatal-infos
diff --git a/.github/workflows/graphs.yml b/.github/workflows/graphs.yaml
similarity index 91%
rename from .github/workflows/graphs.yml
rename to .github/workflows/graphs.yaml
index c86ebb1..a3b3733 100644
--- a/.github/workflows/graphs.yml
+++ b/.github/workflows/graphs.yaml
@@ -6,12 +6,12 @@
   # Run CI on all PRs (against any branch) and on pushes to the main branch.
   pull_request:
     paths:
-      - '.github/workflows/graphs.yml'
+      - '.github/workflows/graphs.yaml'
       - 'pkgs/graphs/**'
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/graphs.yml'
+      - '.github/workflows/graphs.yaml'
       - 'pkgs/graphs/**'
   schedule:
     - cron: '0 0 * * 0' # weekly
diff --git a/.github/workflows/html.yaml b/.github/workflows/html.yaml
index e4eb9fa..e777b79 100644
--- a/.github/workflows/html.yaml
+++ b/.github/workflows/html.yaml
@@ -5,12 +5,12 @@
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/html.yml'
+      - '.github/workflows/html.yaml'
       - 'pkgs/html/**'
   pull_request:
     branches: [ main ]
     paths:
-      - '.github/workflows/html.yml'
+      - '.github/workflows/html.yaml'
       - 'pkgs/html/**'
   schedule:
     - cron: "0 0 * * 0"
diff --git a/.github/workflows/io.yaml b/.github/workflows/io.yaml
index 0c719a6..7733cd6 100644
--- a/.github/workflows/io.yaml
+++ b/.github/workflows/io.yaml
@@ -5,12 +5,12 @@
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/io.yml'
+      - '.github/workflows/io.yaml'
       - 'pkgs/io/**'
   pull_request:
     branches: [ main ]
     paths:
-      - '.github/workflows/io.yml'
+      - '.github/workflows/io.yaml'
       - 'pkgs/io/**'
   schedule:
     - cron: "0 0 * * 0"
diff --git a/.github/workflows/json_rpc_2.yaml b/.github/workflows/json_rpc_2.yaml
index c5b0883..276a4d7 100644
--- a/.github/workflows/json_rpc_2.yaml
+++ b/.github/workflows/json_rpc_2.yaml
@@ -5,12 +5,12 @@
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/json_rpc_2.yml'
+      - '.github/workflows/json_rpc_2.yaml'
       - 'pkgs/json_rpc_2/**'
   pull_request:
     branches: [ main ]
     paths:
-      - '.github/workflows/json_rpc_2.yml'
+      - '.github/workflows/json_rpc_2.yaml'
       - 'pkgs/json_rpc_2/**'
   schedule:
     - cron: "0 0 * * 0"
diff --git a/.github/workflows/mime.yml b/.github/workflows/mime.yaml
similarity index 90%
rename from .github/workflows/mime.yml
rename to .github/workflows/mime.yaml
index 44dbf82..9e49f0e 100644
--- a/.github/workflows/mime.yml
+++ b/.github/workflows/mime.yaml
@@ -1,4 +1,4 @@
-name: Dart CI
+name: package:mime
 
 on:
   schedule:
@@ -7,12 +7,12 @@
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/mime.yml'
+      - '.github/workflows/mime.yaml'
       - 'pkgs/mime/**'
   pull_request:
     branches: [ main ]
     paths:
-      - '.github/workflows/mime.yml'
+      - '.github/workflows/mime.yaml'
       - 'pkgs/mime/**'
 
 jobs:
diff --git a/.github/workflows/oauth2.yml b/.github/workflows/oauth2.yaml
similarity index 95%
rename from .github/workflows/oauth2.yml
rename to .github/workflows/oauth2.yaml
index 0485646..6de86b5 100644
--- a/.github/workflows/oauth2.yml
+++ b/.github/workflows/oauth2.yaml
@@ -1,16 +1,16 @@
-name: Dart CI
+name: package:oauth2
 
 on:
   # Run on PRs and pushes to the default branch.
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/oauth2.yml'
+      - '.github/workflows/oauth2.yaml'
       - 'pkgs/oauth2/**'
   pull_request:
     branches: [ main ]
     paths:
-      - '.github/workflows/oauth2.yml'
+      - '.github/workflows/oauth2.yaml'
       - 'pkgs/oauth2/**'
   schedule:
     - cron: "0 0 * * 0"
diff --git a/.github/workflows/package_config.yaml b/.github/workflows/package_config.yaml
index 416ea1a..2d028b9 100644
--- a/.github/workflows/package_config.yaml
+++ b/.github/workflows/package_config.yaml
@@ -5,12 +5,12 @@
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/package_config.yml'
+      - '.github/workflows/package_config.yaml'
       - 'pkgs/package_config/**'
   pull_request:
     branches: [ main ]
     paths:
-      - '.github/workflows/package_config.yml'
+      - '.github/workflows/package_config.yaml'
       - 'pkgs/package_config/**'
   schedule:
     - cron: "0 0 * * 0"
diff --git a/.github/workflows/source_map_stack_trace.yml b/.github/workflows/source_map_stack_trace.yaml
similarity index 93%
rename from .github/workflows/source_map_stack_trace.yml
rename to .github/workflows/source_map_stack_trace.yaml
index 11d0b10..59cfab1 100644
--- a/.github/workflows/source_map_stack_trace.yml
+++ b/.github/workflows/source_map_stack_trace.yaml
@@ -1,16 +1,16 @@
-name: Dart CI
+name: package:source_map_stack_trace
 
 on:
   # Run on PRs and pushes to the default branch.
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/source_map_stack_trace.yml'
+      - '.github/workflows/source_map_stack_trace.yaml'
       - 'pkgs/source_map_stack_trace/**'
   pull_request:
     branches: [ main ]
     paths:
-      - '.github/workflows/source_map_stack_trace.yml'
+      - '.github/workflows/source_map_stack_trace.yaml'
       - 'pkgs/source_map_stack_trace/**'
   schedule:
     - cron: "0 0 * * 0"
diff --git a/.github/workflows/source_span.yaml b/.github/workflows/source_span.yaml
index 2c2ba05..422d55e 100644
--- a/.github/workflows/source_span.yaml
+++ b/.github/workflows/source_span.yaml
@@ -5,12 +5,12 @@
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/source_span.yml'
+      - '.github/workflows/source_span.yaml'
       - 'pkgs/source_span/**'
   pull_request:
     branches: [ main ]
     paths:
-      - '.github/workflows/source_span.yml'
+      - '.github/workflows/source_span.yaml'
       - 'pkgs/source_span/**'
   schedule:
     - cron: "0 0 * * 0"
diff --git a/pkgs/string_scanner/.github/workflows/test-package.yml b/.github/workflows/string_scanner.yaml
similarity index 84%
rename from pkgs/string_scanner/.github/workflows/test-package.yml
rename to .github/workflows/string_scanner.yaml
index c60f710..665883a 100644
--- a/pkgs/string_scanner/.github/workflows/test-package.yml
+++ b/.github/workflows/string_scanner.yaml
@@ -1,17 +1,28 @@
-name: Dart CI
+name: package:string_scanner
 
 on:
   # Run on PRs and pushes to the default branch.
   push:
-    branches: [ master ]
+    branches: [ main ]
+    paths:
+      - '.github/workflows/string_scanner.yaml'
+      - 'pkgs/string_scanner/**'
   pull_request:
-    branches: [ master ]
+    branches: [ main ]
+    paths:
+      - '.github/workflows/string_scanner.yaml'
+      - 'pkgs/string_scanner/**'
   schedule:
     - cron: "0 0 * * 0"
 
 env:
   PUB_ENVIRONMENT: bot.github
 
+
+defaults:
+  run:
+    working-directory: pkgs/string_scanner/
+
 jobs:
   # Check code formatting and static analysis on a single OS (linux)
   # against Dart dev.
diff --git a/.github/workflows/unified_analytics.yml b/.github/workflows/unified_analytics.yaml
similarity index 89%
rename from .github/workflows/unified_analytics.yml
rename to .github/workflows/unified_analytics.yaml
index f3b3bf4..aa30852 100644
--- a/.github/workflows/unified_analytics.yml
+++ b/.github/workflows/unified_analytics.yaml
@@ -4,12 +4,12 @@
 on:
   pull_request:
     paths:
-      - '.github/workflows/unified_analytics.yml'
+      - '.github/workflows/unified_analytics.yaml'
       - 'pkgs/unified_analytics/**'
   push:
     branches: [ main ]
     paths:
-      - '.github/workflows/unified_analytics.yml'
+      - '.github/workflows/unified_analytics.yaml'
       - 'pkgs/unified_analytics/**'
   schedule:
     - cron: '0 0 * * 0' # weekly
diff --git a/.gitignore b/.gitignore
index 5509140..4fe7cd5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
-*.DS_Store
+# https://dart.dev/tools/pub/private-files
+.dart_tool
+pubspec.lock
diff --git a/README.md b/README.md
index f3281a8..0e2614e 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@
 | [file_testing](pkgs/file_testing/) | Testing utilities for package:file. | [![package issues](https://img.shields.io/badge/package:file_testing-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Afile_testing) | [![pub package](https://img.shields.io/pub/v/file_testing.svg)](https://pub.dev/packages/file_testing) |
 | [graphs](pkgs/graphs/) | Graph algorithms that operate on graphs in any representation. | [![package issues](https://img.shields.io/badge/package:graphs-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Agraphs) | [![pub package](https://img.shields.io/pub/v/graphs.svg)](https://pub.dev/packages/graphs) |
 | [html](pkgs/html/) | APIs for parsing and manipulating HTML content outside the browser. | [![package issues](https://img.shields.io/badge/package:html-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ahtml) | [![pub package](https://img.shields.io/pub/v/html.svg)](https://pub.dev/packages/html) |
-| [io](pkgs/io/) | Utilities for the Dart VM Runtime including support for ANSI colors, file copying, and standard exit code values. | [![pub package](https://img.shields.io/pub/v/io.svg)](https://pub.dev/packages/io) |
+| [io](pkgs/io/) | Utilities for the Dart VM Runtime including support for ANSI colors, file copying, and standard exit code values. | [![package issues](https://img.shields.io/badge/package:io-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aio) | [![pub package](https://img.shields.io/pub/v/io.svg)](https://pub.dev/packages/io) |
 | [json_rpc_2](pkgs/json_rpc_2/) | Utilities to write a client or server using the JSON-RPC 2.0 spec. | [![package issues](https://img.shields.io/badge/package:json_rpc_2-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ajson_rpc_2) | [![pub package](https://img.shields.io/pub/v/json_rpc_2.svg)](https://pub.dev/packages/json_rpc_2) |
 | [markdown](pkgs/markdown/) | A portable Markdown library written in Dart that can parse Markdown into HTML. | [![pub package](https://img.shields.io/pub/v/markdown.svg)](https://pub.dev/packages/markdown) |
 | [mime](pkgs/mime/) | Utilities for handling media (MIME) types, including determining a type from a file extension and file contents. | [![package issues](https://img.shields.io/badge/package:mime-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Amime) | [![pub package](https://img.shields.io/pub/v/mime.svg)](https://pub.dev/packages/mime) |
@@ -45,11 +45,12 @@
 | [stack_trace](pkgs/stack_trace/) | A package for manipulating stack traces and printing them readably. | [![package issues](https://img.shields.io/badge/package:stack_trace-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Astack_trace) | [![pub package](https://img.shields.io/pub/v/stack_trace.svg)](https://pub.dev/packages/stack_trace) |
 | [stream_channel](pkgs/stream_channel/) | An abstraction for two-way communication channels based on the Dart Stream class. | [![package issues](https://img.shields.io/badge/package:stream_channel-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Astream_channel) | [![pub package](https://img.shields.io/pub/v/stream_channel.svg)](https://pub.dev/packages/stream_channel) |
 | [stream_transform](pkgs/stream_transform/) | A collection of utilities to transform and manipulate streams. | [![package issues](https://img.shields.io/badge/package:stream_transform-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Astream_transform) | [![pub package](https://img.shields.io/pub/v/stream_transform.svg)](https://pub.dev/packages/stream_transform) |
+| [string_scanner](pkgs/string_scanner/) | A class for parsing strings using a sequence of patterns. | [![package issues](https://img.shields.io/badge/package:string_scanner-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Astring_scanner) | [![pub package](https://img.shields.io/pub/v/string_scanner.svg)](https://pub.dev/packages/string_scanner) |
 | [term_glyph](pkgs/term_glyph/) | Useful Unicode glyphs and ASCII substitutes. | [![package issues](https://img.shields.io/badge/package:term_glyph-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aterm_glyph) | [![pub package](https://img.shields.io/pub/v/term_glyph.svg)](https://pub.dev/packages/term_glyph) |
 | [test_reflective_loader](pkgs/test_reflective_loader/) | Support for discovering tests and test suites using reflection. | [![package issues](https://img.shields.io/badge/package:test_reflective_loader-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Atest_reflective_loader) | [![pub package](https://img.shields.io/pub/v/test_reflective_loader.svg)](https://pub.dev/packages/test_reflective_loader) |
 | [timing](pkgs/timing/) | A simple package for tracking the performance of synchronous and asynchronous actions. | [![package issues](https://img.shields.io/badge/package:timing-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Atiming) | [![pub package](https://img.shields.io/pub/v/timing.svg)](https://pub.dev/packages/timing) |
 | [unified_analytics](pkgs/unified_analytics/) | A package for logging analytics for all Dart and Flutter related tooling to Google Analytics. | [![package issues](https://img.shields.io/badge/package:unified_analytics-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aunified_analytics) | [![pub package](https://img.shields.io/pub/v/unified_analytics.svg)](https://pub.dev/packages/unified_analytics) |
-| [watcher](pkgs/watcher/) | Monitor directories and send notifications when the contents change. | [![package issues](https://img.shields.io/badge/package:watcher-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Awatcher) | [![pub package](https://img.shields.io/pub/v/watcher.svg)](https://pub.dev/packages/watcher) |
+| [watcher](pkgs/watcher/) | A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. | [![package issues](https://img.shields.io/badge/package:watcher-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Awatcher) | [![pub package](https://img.shields.io/pub/v/watcher.svg)](https://pub.dev/packages/watcher) |
 | [yaml](pkgs/yaml/) | A parser for YAML, a human-friendly data serialization standard | [![package issues](https://img.shields.io/badge/package:yaml-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ayaml) | [![pub package](https://img.shields.io/pub/v/yaml.svg)](https://pub.dev/packages/yaml) |
 | [yaml_edit](pkgs/yaml_edit/) | A library for YAML manipulation with comment and whitespace preservation. | [![package issues](https://img.shields.io/badge/package:yaml_edit-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ayaml_edit) | [![pub package](https://img.shields.io/pub/v/yaml_edit.svg)](https://pub.dev/packages/yaml_edit) |
 
diff --git a/pkgs/string_scanner/.github/dependabot.yml b/pkgs/string_scanner/.github/dependabot.yml
deleted file mode 100644
index a19a66a..0000000
--- a/pkgs/string_scanner/.github/dependabot.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Set update schedule for GitHub Actions
-# See https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/keeping-your-actions-up-to-date-with-dependabot
-
-version: 2
-updates:
-
-- package-ecosystem: github-actions
-  directory: /
-  schedule:
-    interval: monthly
-  labels:
-    - autosubmit
-  groups:
-    github-actions:
-      patterns:
-        - "*"
diff --git a/pkgs/string_scanner/.github/workflows/publish.yaml b/pkgs/string_scanner/.github/workflows/publish.yaml
deleted file mode 100644
index 27157a0..0000000
--- a/pkgs/string_scanner/.github/workflows/publish.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-# A CI configuration to auto-publish pub packages.
-
-name: Publish
-
-on:
-  pull_request:
-    branches: [ master ]
-  push:
-    tags: [ 'v[0-9]+.[0-9]+.[0-9]+' ]
-
-jobs:
-  publish:
-    if: ${{ github.repository_owner == 'dart-lang' }}
-    uses: dart-lang/ecosystem/.github/workflows/publish.yaml@main
-    permissions:
-      id-token: write # Required for authentication using OIDC
-      pull-requests: write # Required for writing the pull request note
diff --git a/tool/pubspec.yaml b/tool/pubspec.yaml
new file mode 100644
index 0000000..1517197
--- /dev/null
+++ b/tool/pubspec.yaml
@@ -0,0 +1,9 @@
+name: readme_update
+publish_to: none
+environment:
+  sdk: ^3.6.0
+
+dependencies:
+  path: ^1.9.1
+  pubspec_parse: ^1.5.0
+  yaml: ^3.1.3
diff --git a/tool/readme_update.dart b/tool/readme_update.dart
new file mode 100644
index 0000000..ab0ca5a
--- /dev/null
+++ b/tool/readme_update.dart
@@ -0,0 +1,66 @@
+// Copyright (c) 2025, 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:collection';
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:pubspec_parse/pubspec_parse.dart';
+import 'package:yaml/yaml.dart';
+
+Future<void> main(List<String> args) async {
+  // assume we're being run from the root of the `tools` directory
+  final descriptions = SplayTreeMap<String, String>();
+
+  // * Enumerate all packages in `pkgs/`
+  for (var directory in Directory(p.join(Directory.current.path, 'pkgs'))
+      .listSync()
+      .whereType<Directory>()) {
+    final pubspecFile = File(p.join(directory.path, 'pubspec.yaml'));
+    final pubspec = Pubspec.parse(pubspecFile.readAsStringSync(),
+        sourceUrl: pubspecFile.uri);
+
+    assert(p.basename(directory.path) == pubspec.name);
+
+    // * Grab the `description` field from their pubspec files
+    descriptions[pubspec.name] = pubspec.description!;
+  }
+
+  // * Ensure all packages have a file in `.github/workflows`
+  for (var entry in descriptions.entries) {
+    final workflowFile = File(p.join('.github/workflows', '${entry.key}.yaml'));
+
+    final workflowYaml =
+        loadYaml(workflowFile.readAsStringSync(), sourceUrl: workflowFile.uri)
+            as YamlMap;
+
+    final workflowName = workflowYaml['name'] as String;
+    // * Ensure each has a name `package:[pkg name]`
+    assert(workflowName == 'package:${entry.key}');
+  }
+
+  // * Print out the readme table!
+
+  print('''
+| Package | Description | Issues | Version |
+| --- | --- | --- | --- |''');
+
+  for (var entry in descriptions.entries) {
+    final pkgName = entry.key;
+    final name = '[$pkgName](pkgs/$pkgName/)';
+
+    /*
+     [![package issues](https://img.shields.io/badge/package:bazel_worker-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Abazel_worker)
+     [![pub package](https://img.shields.io/pub/v/bazel_worker.svg)](https://pub.dev/packages/bazel_worker) |
+
+     */
+
+    final issues =
+        '[![package issues](https://img.shields.io/badge/package:$pkgName-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3A$pkgName)';
+    final version =
+        '[![pub package](https://img.shields.io/pub/v/$pkgName.svg)](https://pub.dev/packages/$pkgName)';
+
+    print(['', name, entry.value, issues, version, ''].join(' | ').trim());
+  }
+}