fluttersdk_telescope 0.0.5 copy "fluttersdk_telescope: ^0.0.5" to clipboard
fluttersdk_telescope: ^0.0.5 copied to clipboard

Passive runtime inspector for Flutter. Captures HTTP, logs, exceptions, DB queries, and Magic events via VM Service extensions. CLI tail and MCP tools for Claude Code.

Changelog #

All notable changes to this project will be documented in this file.

This project follows Semantic Versioning 2.0.0. Entries follow the Keep a Changelog shape.


[Unreleased] #


0.0.5 - 2026-08-25 #

Added #

  • A tenth ring buffer, FramePerfRecord, for per-frame performance data. Carries the fields FrameTiming exposes (buildMicros, rasterMicros, vsyncOverheadMicros, totalSpanMicros) plus a per-frame block-attribution map. TelescopeStore.recordFramePerf/.recentFramePerf/.onFramePerfRecord follow the shape of the existing nine buffers, with one deliberate difference: the buffer reads its own setFramePerfCapacity (default 3600, about a minute at 60fps) instead of the shared setCapacity, so a useful frame window does not simultaneously inflate the HTTP, log, exception, dump, model, cache, event, gate and query buffers. clearFramePerf() is a new public, non-test-only method that empties only this buffer, for a production caller that needs to zero it at the start of a measurement session without touching the other nine. (lib/src/records/frame_perf_record.dart, lib/src/telescope_store.dart, lib/telescope.dart)

  • FramePerfWatcher, which fills that buffer by joining the two sources the engine reports separately. Frame magnitude comes from SchedulerBinding.addTimingsCallback, which carries FrameTiming.frameNumber but arrives late and batched (roughly 100ms later on web). Per-frame attribution comes from draining FlutterTimeline at the end of every frame, which knows what ran but not which frame number it was. So the drain parks its block map in a bounded pending map and the timings callback is the sole emission point, writing one complete record per timing. A timing with no block map still emits, because frame magnitude without attribution is still worth having; a block map whose timing never arrives is dropped by the bound.

    Two things about the drain are easy to get wrong and are pinned in the source. It re-registers itself as its last act, because addPostFrameCallback is one-shot and a drain that does not would run exactly once, freezing the liveness counter at 1 and making every later session unreportable. And the collect runs in a MICROTASK scheduled by the post-frame callback rather than inline in it: SchedulerBinding.handleDrawFrame wraps the whole post-frame phase in its own POST_FRAME span, so an inline collect runs with a non-empty nesting stack and trips assert(_stackPointer == 0) in FlutterTimeline. That assert is stripped outside debug, where the same call would instead write a stale start time into the freshly swapped buffer and report an enormous duration for a block that never ran.

    The watcher also exposes a static, monotonic livenessCounter incremented once per frame drawn. It is the only reliable proof the engine is rendering: SchedulerBinding.framesEnabled was measured reporting true, with a resumed lifecycle and an armed onReportTimings, on a Chrome page that had produced one frame in two seconds. The watcher is opt-in and is NOT auto-installed by TelescopePlugin.install(); register it with TelescopePlugin.registerWatcher(FramePerfWatcher()). (lib/src/watchers/frame_perf_watcher.dart, lib/telescope.dart)

  • ext.telescope.frames, telescope:frames and telescope_frames, the wire surface for the new frame-perf buffer. The extension returns the buffer's records alongside FramePerfWatcher.livenessCounter, so a caller reading an empty result can tell a quiet app from a stalled engine without a second round trip. The naming follows telescope's plural-noun convention (requests, exceptions, queries, ...); the wire says frames, not frame_perf, even though the Dart types keep that prefix to name the record and the watcher. TelescopeArtisanProvider now ships 7 CLI commands and 10 MCP tools. (lib/src/extensions/register_telescope_extensions.dart, lib/src/commands/telescope_frames_command.dart, lib/src/telescope_artisan_provider.dart, test/src/commands/telescope_frames_command_test.dart)

Changed #

  • The registry dispatch fires on a published release now, not on every push that touches the skill. Under the push trigger fluttersdk/ai climbed to v1.3.75, and most of those releases re-published identical skill content: a docs commit and a release commit each cost the registry a version. The registry version now tracks published telescope releases instead of counting commits. workflow_dispatch stays as the manual escape hatch when a skill fix has to reach users before the next release. (.github/workflows/dispatch-to-registry.yml)

Fixed #

  • The registry dispatch could never fire, so a release would have shipped a skill the registry never received. dispatch-to-registry.yml declared release: [published], but publish.yml creates the release with softprops/action-gh-release under the default GITHUB_TOKEN, and GitHub does not start workflow runs from events raised by that token. The run history confirms it: no release event has ever appeared there, only the manual run and the retired push trigger. It has also had no chance to be noticed, since the switch to that trigger landed after the last release (0.0.4, 2026-06-17). The failure mode is silent, because publish.yml goes green either way and the only symptom is a user installing a skill one release behind. publish.yml now calls the workflow directly with needs: github-release, which removes the cross-workflow event and sequences the dispatch after pub.dev has accepted the release rather than alongside it; workflow_call replaces the dead trigger and workflow_dispatch stays as the manual escape hatch. Secrets are passed by name rather than inherited, since the called workflow needs exactly two. Second bug in the same file, fixed alongside: the version extractor tested the ref against ^v[0-9]+\.[0-9]+\.[0-9]+, but this repo tags without the v prefix, so that branch never matched a real tag and always fell through to reading pubspec.yaml. (.github/workflows/dispatch-to-registry.yml, .github/workflows/publish.yml)

  • doc/mcp/tool-reference.md still promised newest-first records and a 200-entry cap. 0.0.3 corrected the MCP tool descriptions to the real wire shape and retired the newest-first shorthand, but this page kept it in all eight limit rows, plus a "typically 200-500 depending on buffer type" capacity note. limit returns the most recent N records in chronological order (_trim takes list.sublist(list.length - limit), so the oldest of that window is first and the newest is last), and every buffer caps at 500. A client reading this page and not reversing its iteration displayed the window backwards. (doc/mcp/tool-reference.md)

  • telescope_tail's MCP schema told agents the log buffer holds 200 entries; it holds 500. The limit parameter description read "enforced by the ring-buffer size, typically 200" while TelescopeStore._cap is 500 and every other tool descriptor (telescope_requests, telescope_exceptions, telescope_events, telescope_gates, telescope_queries) correctly said 500. An agent budgeting a tail window against the documented number would under-read by 300 entries and conclude records had been evicted when they were still in the buffer. (lib/src/telescope_artisan_provider.dart)

  • CI could not have passed on its next run, and publish.yml would have blocked the release with it. The Flutter tool ships an analysis_options.yaml migrator that appends an analyzer.exclude block for build/ plus the six platform runner directories, and it runs on every flutter pub get. That is the first step of both ci.yml and publish.yml, so by the time dart pub publish --dry-run ran later in the same job the checkout was dirty: 1 checked-in file is modified in git, exit 65. Reproduced on a clean checkout of master with the exit code read directly rather than through a pipe. Nothing had reported it because no CI run here postdates the migrator. Both files now carry the block the migrator wants, which makes it a no-op; the hand-written comment above it survives a pub get, since the migrator reads the parsed YAML, finds the excludes present and skips the file. Reverting the file inside the workflow was rejected as the alternative: it hides the drift and leaves every contributor's tree dirty after a pub get. The same fix landed in fluttersdk_wind (#178) and fluttersdk_wind_diagnostics_contracts (#1). (analysis_options.yaml, example/analysis_options.yaml)

0.0.4 - 2026-06-17 #

Changed #

  • fluttersdk_artisan constraint bumped ^0.0.6 -> ^0.0.8. Required for co-installability with fluttersdk_dusk 0.0.7, which declares fluttersdk_artisan: ^0.0.8. Without this bump, a downstream package listing both fluttersdk_dusk: ^0.0.7 and fluttersdk_telescope would fail pub dependency resolution. No public API change; constraint only.
  • telescope:install now injects import 'package:magic_devtools/telescope.dart'; and gates the Magic-stack wiring on the magic_devtools dependency instead of the removed package:magic/telescope_integration.dart. Coordinated with the magic_devtools extraction that moved MagicTelescopeIntegration (plus the 5 Magic watchers and MagicHttpFacadeAdapter) out of the magic core package. The injected MagicTelescopeIntegration.install() call and all other wiring are unchanged.

Fixed #

  • telescope:install no longer injects the magic_devtools import when lib/main.dart has no await Magic.init( anchor. Previously, the magic-side wiring block fired for any project that listed magic_devtools in pubspec regardless of whether the app called Magic.init. On a vanilla Flutter app this left an unused import that broke dart analyze in the consumer. The block is now gated on hasMagicInit && _hasMagicDevtoolsDep() so the import and MagicTelescopeIntegration.install() call are only injected for Magic-stack apps that actually call Magic.init. The existing try/catch around injectAfterMagicInit is retained as a defensive backstop.

Documentation #

  • Synced docs, skill files, README.md, and CLAUDE.md to the magic_devtools extraction and the fluttersdk_artisan ^0.0.8 bump. The Magic-stack telescope adapter is now documented as shipping in magic_devtools (imported via package:magic_devtools/telescope.dart, added as a dev_dependency); the installation / quickstart / watchers pages, the MCP setup snippet, and the skill (SKILL.md + references) reflect the magic_devtools dependency gate and import. Dependency-version pins bumped from ^0.0.3 to ^0.0.4 across README.md, doc/getting-started/installation.md, doc/getting-started/quickstart.md, and doc/mcp/setup.md; skill version stamp bumped to 0.0.4.

0.0.3 - 2026-05-28 #

Added #

  • Skill v0.0.3: new ## 8. Community: star + issue (optional, once per session) section in skills/fluttersdk-telescope/SKILL.md plus a new skills/fluttersdk-telescope/references/community.md reference page. Trigger split: star CTA fires after the user confirms a telescope task end-to-end (captured HTTP record after a gesture, level-filtered tail slice, surfaced uncaught exception, clear-then-repro delta, or clean telescope:install); issue CTA fires only on a genuine telescope-side bug (malformed MCP envelope, kInvalidParams for documented params, TelescopeStore losing entries before the 500-cap, clear returning anything but {"cleared": true}, shipped watchers throwing on a clean install, telescope:install exiting non-zero on a fresh consumer, or registerExtensionIdempotent violating idempotency). Issue CTA explicitly excludes the documented wired-but-empty buffers, swallowed try / catch invisibility, consumer-app exceptions, raw dart:io HttpClient traffic gaps, the missing telescope_models MCP tool, and FIFO eviction past 500. Preflight gates on gh presence and auth; failure prints the URL only, no open / xdg-open / start. Both CTAs are prose-permission (not AskUserQuestion), maximum one star and one issue per session, declining one suppresses only that CTA. Labels: only bug is applied (the agent-reported label does not exist on fluttersdk/telescope, drop the flag).
  • Repo flow adopted GitHub Flow (single long-lived master; retired the develop accumulator). CLAUDE.md and .github/copilot-instructions.md now carry Golden Rule 7 plus a ## Branching section documenting task-branch naming, squash-merge policy, and the release-tag shape. delete_branch_on_merge: true enabled on origin so merged branches auto-cleanup.

Changed #

  • fluttersdk_artisan constraint bumped ^0.0.4 -> ^0.0.6. Consumers were already pulling 0.0.6 transitively (via the post-install fluttersdk_artisan: any line the telescope:install bootstrap appends to the consumer pubspec); telescope's own dev resolution now tracks the same version so tests, format, and pub publish --dry-run run against the artisan that consumers actually execute. Picks up the 0.0.5 + 0.0.6 fixes: _plugins.g.dart AOT staleness detection, MCP serverInfo.version sync to 0.0.6, atomic .mcp.json writes via .tmp + rename, the mcp:install --invocation plugin-aware fallback, and the dusk_evaluate VM-routed fix. Future artisan 0.0.7 will need a coordinated bump.
  • telescope_* MCP tool descriptions now state the actual wire shape ("oldest-first; last entry is newest"). Previously seven of the eight read tools claimed "Returns newest-first" while the handler delivered oldest-first; the SKILL.md Law 5 disclaimer ("presenter shorthand") that papered over the gap has been retired. Clients reading the description verbatim no longer assume a reversed order.
  • mcp:install fallback now writes dart run fluttersdk_telescope mcp:serve when bin/fsa is absent (via the wrapper's --invocation pass-through to artisan's mcp:install, gated on the 0.0.6 trim-whitespace behavior).
  • telescope:install no longer depends on the AOT-compiled bin/fsa. The chained subprocess calls (install + plugin:install fluttersdk_telescope) now spawn dart run fluttersdk_telescope ... directly through the telescope CLI wrapper, mirroring the Cat C subprocess pattern landed in fluttersdk_dusk. Consumers on a clean checkout (where fsa has not been compiled yet) can complete the bootstrap chain without a ProcessException: No such file or directory failure. Behavior delta: even consumers with bin/fsa scaffolded now invoke plugin:install through dart run (a few seconds slower than the fsa AOT proxy on a single telescope:install invocation). Requires dart on PATH (always true on a Flutter dev box). Matches dusk's unconditional dart run pattern for cross-plugin consistency.

Fixed #

  • telescope_clear MCP descriptor claimed it cleared "three ring buffers (http, logs, exceptions)" but the implementation has always wiped all 9 buffers atomically (per Core Law 6). Rewrote the description and Usage bullets in lib/src/telescope_artisan_provider.dart to enumerate the 9 buffers (http, logs, exceptions, events, gates, dumps, queries, caches, magic models), document the {"cleared": true} envelope, and make the upstream-sink isolation (Sentry, Bugsnag still receive events) explicit. The wire behavior was already correct; this is a descriptor-string fix only.
  • bin/fluttersdk_telescope.dart now forces collectMcpTools: true when dispatching mcp:serve, so dart run fluttersdk_telescope mcp:serve surfaces all 9 telescope_* MCP tools. Previously returned 0 plugin tools.

0.0.2 - 2026-05-22 #

Fixed #

  • CHANGELOG correction. The 0.0.1 archive on pub.dev shipped with a populated [Unreleased] block left over from release prep: every entry listed there (magic dev-dep drop, pubspec_overrides.yaml removal, test/src/magic/ deletion, magic tag cleanup in dart_test.yaml + CI workflows + agent-instruction files, example_magic/ removal, sub-barrel import path swap in telescope:install) actually shipped INSIDE 0.0.1; nothing was published before it. This 0.0.2 republishes the corrected CHANGELOG so the consolidated 0.0.1 history surfaces on pub.dev.
  • README pinned-install snippet bumped from ^0.0.1 to ^0.0.2 so the example matches the published version.

Unchanged #

  • No code, no test, no runtime behavior, no public API surface changed. lib/, bin/, test/, example/, and all 11 ext.telescope.* VM Service extensions plus 9 telescope_* MCP tools are byte-identical to 0.0.1.

0.0.1 - 2026-05-22 #

Initial public release of fluttersdk_telescope. Passive runtime inspector for Flutter apps with a framework-agnostic core and optional Magic-stack integration. Plugin of fluttersdk_artisan ^0.0.4 (hosted-only; no path overrides). Vanilla-Flutter clean: zero magic references in the production or default-test surface. Magic-stack integration is opt-in via runtime detection in telescope:install, which injects import 'package:magic/telescope_integration.dart'; and an if (kDebugMode) MagicTelescopeIntegration.install(); block after await Magic.init( when the consumer's pubspec lists magic:.

Watchers #

9 watchers across vanilla Flutter and Magic-stack:

  • LogWatcher (auto-installed): package:logging Logger calls captured to the logs ring buffer.
  • ExceptionWatcher: FlutterError.onError + PlatformDispatcher.instance.onError, chain-preserve previous handlers.
  • DumpWatcher: debugPrint capture (vanilla Flutter); debug-only.
  • MagicHttpFacadeAdapter: Magic Http facade interceptor.
  • MagicModelWatcher: ModelCreated / ModelSaved / ModelDeleted events from Magic.
  • MagicCacheWatcher: CacheHit / CacheMiss / CachePut / CacheForget / CacheFlush events.
  • MagicEventWatcher: curated event subscription (auth, db connection, gate-define).
  • MagicGateWatcher: GateAccessChecked event after every Gate.allows / Gate.denies.
  • MagicQueryWatcher: QueryExecuted event from the magic database connector.

Records #

9 immutable record types: HttpRequestRecord, LogRecordEntry, ExceptionRecord, MagicModelRecord, MagicCacheRecord, EventRecord, GateRecord, DumpRecord, QueryRecord.

9-buffer TelescopeStore #

Per-buffer Queue

VM Service extensions (11) #

ext.telescope.requests, .console, .exceptions, .events, .gates, .dumps, .queries, .caches, .clear, .pause, .resume. Every registration goes through registerExtensionIdempotent (from fluttersdk_artisan) for hot-restart safety.

MCP tools (9) #

telescope_tail, telescope_requests, telescope_exceptions, telescope_clear, telescope_events, telescope_gates, telescope_dumps, telescope_queries, telescope_caches. Each is a McpToolDescriptor const instance contributed via TelescopeArtisanProvider.mcpTools().

CLI commands (6) #

telescope:install, telescope:tail, telescope:requests, telescope:queries, telescope:caches, telescope:clear. telescope:install is a one-shot bootstrap that scaffolds the consumer artisan harness, runs plugin:install fluttersdk_telescope, and injects TelescopePlugin.install() into lib/main.dart (Magic-stack anchor or vanilla runApp anchor).

Three public contracts #

  • TelescopeWatcher: name getter + install() + uninstall().
  • TelescopeHttpAdapter: same 3-method shape + optional pendingCount getter (default 0).
  • McpToolDescriptor: const-constructible; shape owned by fluttersdk_artisan.

TelescopeStore extension surface #

  • pendingHttpCount getter sums TelescopeHttpAdapter.pendingCount across every registered adapter; consumed by ext.dusk.wait_for_network_idle for network-idle detection.

CI + automated publishing #

  • .github/workflows/ci.yml: format + analyze + flutter test (--exclude-tags integration) + 80% line-coverage floor (lcov + awk gate) + codecov upload + dart pub publish --dry-run.
  • .github/workflows/publish.yml: SemVer tag push triggers validate -> pub.dev publish via the official dart-lang/setup-dart/.github/workflows/publish.yml@v1 reusable workflow with OIDC authentication + github-release job auto-extracting CHANGELOG entry.
  • .github/dependabot.yml: weekly pub root + weekly github-actions bumps.

Documentation #

  • README.md two-path Quick Start (one-shot self-bootstrap via dart run fluttersdk_telescope telescope:install; manual wiring for consumers who prefer to drive the artisan dispatcher by hand). After install, the consumer's ./bin/fsa native AOT launcher is the recommended entry point for every subsequent telescope command.
  • doc/ tree: getting-started/, watchers/, mcp/.
  • llms.txt at repo root per llmstxt.org spec.
  • skills/fluttersdk-telescope/ LLM-agent skill (SKILL.md + 2 references).

Compatibility #

  • Dart SDK >=3.4.0 <4.0.0; Flutter >=3.22.0.
  • Platforms: Android, iOS, macOS, Linux, Windows, Web (debug-only on every platform; release builds tree-shake the entire telescope subsystem via kDebugMode gate).
  • Magic-stack integration optional. Vanilla Flutter consumers use Dio adapter + LogWatcher + ExceptionWatcher + DumpWatcher with no Magic dependency.

Test coverage #

249 tests green at release time across watchers, records, commands, extensions, and the artisan provider. 80% line coverage floor enforced in CI (current measured coverage 95.60%).

3
likes
160
points
4.58k
downloads

Documentation

Documentation
API reference

Publisher

verified publisherfluttersdk.com

Weekly Downloads

Passive runtime inspector for Flutter. Captures HTTP, logs, exceptions, DB queries, and Magic events via VM Service extensions. CLI tail and MCP tools for Claude Code.

Homepage
Repository (GitHub)
View/report issues

Topics

#mcp-server #ai-agents #inspector #observability #debugging

License

MIT (license)

Dependencies

flutter, fluttersdk_artisan, logging, meta

More

Packages that depend on fluttersdk_telescope