dartograph 0.16.1
dartograph: ^0.16.1 copied to clipboard
Evidence-backed dependency graph analysis for Dart and Flutter codebases.
Changelog #
A Korean version of this changelog is kept in CHANGELOG.ko.md.
Unreleased #
0.16.1 #
- Stop reporting table-valued SQL functions as table relations; preserve unresolved evidence and subsequent real tables.
0.16.0 #
- Compatibility: the new
routes --role client,impact --format language-traversalandschemaoutputs need isthmus at commit76b6141or later; the published isthmus-cli 0.9.0 does not accept them. The vendored isthmus conformance vectors are locked at76b6141. Existing commands and their default output are unchanged, except that object-pattern reads can remove previousdeadfindings and addimpactresults (see below). - New
routes --role client [--wrappers <file>] [--include-tests] [--service <name>] [--project <shared-root>] <package-root>command emits isthmus httproute-callfacts (bridge-facts v1,target: "http",roles: ["client"]) for package:http, dio, retrofit.dart, chopper and declaredhttp-wrappersv1 dart wrappers. Base-URL joins follow each library's source (dio string concatenation, retrofit.dart's two-step base resolution plus dio join, chopper's generated join plus client slash join), verified against requests the real libraries sent to a local mock server.symbol.usris the enclosing declaration's dartograph id. The shared isthmus conformance vectors (http-template, url-compose, http-limitation-scope) are vendored with a lock file and pass 101/101 producer cases. - New
impact --format language-traversalemits an isthmuslanguage-traversalv1 document fortrace: a single multi-root pass with per-declaration root lists, shortest-path witnesses,direct/candidateevidence,revision(explicit or clean git HEAD) andgraphRevision. Unknown roots are listed withroot-not-foundand exit 64. The defaultimpactoutput is unchanged. - New
schema --format json [--project <shared-root>] <package-root>command emits isthmus persistencerelation-usefacts (bridge-facts v1,target: "persistence") for sqflite, sqlite3, postgres, drift (tables, custom queries,.driftfiles) and floor, plus uppercase SQL string literals, so isthmus can join Dart code with a schemagraph SQL catalog. The SQL reader is a port of the shared kartograph/cartograph extractor. Non-literal SQL is kept as dynamic facts; non-SQL stores and unsupported SQL packages are reported as limitations. Existing commands are unchanged. schemafacts from Dart sources now carrysymbol.usr, the enclosing declaration's dartograph id (the same helper and id space asroutes --role clientandimpact), so isthmustracecan connect handler/client reach to relation uses. Only files that produced facts are resolved. Facts without an identity (.driftfiles, files outside the analyzed source directories) are counted by themissing-relation-usrslimitation.- Fields and getters read only through Dart object patterns (
if (x case Foo(:final bar)),switchcases such asFoo(bar: > 0), destructuringvar Foo(:bar) = x, including extension getters) now record a read edge. Previously they were reported bydeadand missed byimpact. Record pattern fields are not graph declarations and are unchanged. The analysis cache identity is bumped so stale results are discarded. - Document that constructors (default, named, and factory) are modeled as their enclosing class: constructor IDs are not graph nodes, unused constructors are not reported by
dead, andimpactis class-granular for constructor changes. Analysis behavior is unchanged.
0.15.2 #
- Invoking a callable object (
validator('x'),holder.validator('y')) now records a call edge to its implicitcallmethod, and an implicitcalltear-off records a reference edge. Previously suchcallmethods were reported as dead and missed byimpact. The analysis cache identity is bumped so stale results are discarded.
0.15.1 #
- Adopt the selected Scout bird mascot in both READMEs and the VS Code extension icon.
- Include the recorded CLI demo and expanded tool-adoption results in the package documentation, preserving the experiment's grading limitations.
- Document the completed
coden.krverified publisher setup and clarify extraction timestamps versus optional source modification times in the graph exchange guide. - Analysis behavior and CLI arguments are unchanged.
0.15.0 #
-
setup --target claude|cursor|codex|opencodeandsetup --uninstallconfigure or remove agent integrations while preserving unrelated settings. Claude setup also maintains the repository routing block for tool discovery. -
query --with-source [--source-context N]includes declaration source lines.cycles,rules,metrics,affected, andcomparesupport text/JSON/SARIF report formats; query remains JSON-only. -
impact --format test-listemits test paths for selected test execution. The repository includes a composite GitHub Action and SARIF setup guidance. -
Publish the bridge-facts exchange guide and align its RN global-event and external-retention contracts with the sister tools. This does not add RN source extraction to dartograph.
-
Add an agent benchmark harness and record tool-adoption experiments; observed adoption is specific to the recorded models/tasks and is not a general coding-performance claim.
-
Opt-in
--workspaceaggregates a pub workspace: when the rootpubspec.yamldeclaresworkspace:members, index-consuming commands (graph,query,dead,deps,dup,cycles,rules,metrics,impact,baseline,runtime) analyze the root and every member package together, with memberlib//test//bin//example/sources, barrels, plugins, andbuild.yamlentry points retained per package.deps --workspaceaudits each package against its own pubspec and attributes findings with amanifestfield (text/Markdown columns, GitHub Actionsfile=, and SARIF artifact URIs point at the owning pubspec). Memberdartograph.yamlfiles are ignored and reported; unreadable or symlinked member paths are skipped and reported, and the flag fails the analysis (exit 2) when the root pubspec declares noworkspace:members or every declared member is skipped. Aggregate results use separate cache keys from single-package scans. MCP tools accept a matchingworkspaceboolean. -
New read-only MCP tool
dartograph_exploreis a single entry point: passpackageRootplus exactly one question shape and it routes internally —symbol/batchtodependency_query(withwithSourcedefaulting on andsourceContext3),impactSymbol/since/changedtoimpact_query,commandtoverify_run, andcommand: "runtime"toruntime_query. The first response line names the routed path, arguments that do not apply to the routed shape are rejected instead of silently ignored, and a call with no shape returns a routing menu. Setting the server environment variableDARTOGRAPH_MCP_LEGACY_TOOLSto0orfalsemakestools/listadvertise onlydartograph_explore; the narrower tools stay callable.
0.14.0 #
- The MCP server keeps a per-session incremental cache in a
dartograph-mcp-cache.*temporary directory with one subdirectory perpackageRoot, so repeated queries in one session reuse file-level fact caches. Cache creation or write failures fall back to equivalent full analysis with a path-free diagnostic on stderr, and the cache directory is removed when the session ends (#117). - New read-only MCP tool
runtime_queryrunsruntime --no-verify --format jsondetection with a fixed argument list: it accepts onlypackageRootand an optionallimit, and rejects execution, environment injection, recording, and filter arguments (#117). runtime --kinds <csv>narrows the reported fact categories (env,dynamicLoad,config,asset,external) andruntime --statuses <csv>narrows the verdict sections (present,defaulted,missing,unverified). Filters narrow reported lists only — risk, limitations, and exit-code semantics still describe the full analysis, and--limitapplies to the filtered lists.--statusesdoes not combine with--no-verify; unknown, empty, or duplicated values are usage errors (#117).- Runtime reports carry
unverifiedReasonCounts, a deterministic tally of unverified facts grouped by their reason prefix (unspecifiedfor reasons without one), serialized in JSON, text, markdown, and SARIF outputs (#117).
0.13.0 #
runtime --executeunderdart install/dart compile exebuilds resolves the Dart SDK through the executable name,DART_SDK, thenPATH, and reports a reason instead of re-running itself.DynamicLibrary.openbare library names stay unverifiable rather than failing a file check, andexternalnetwork detection reports only http(s) literals atUri.parseor known network-API argument positions (#108).dupfindings now sort by a total order — same-length three-way duplicates compare the second instance too — anddead'sredundantPublicno longer flags declarations retained structurally (sealed subtypes, containers of reachable members): they are unobserved, not internal-only (#110).- Symlinks whose targets escape the package root are still analyzed like
the analyzer does, but are now surfaced as
symlink-escape:limitations across analysis,bridges, andruntimeoutputs (#111). - MCP stdio framing bounds each JSON-RPC line to 1 MiB (oversized messages
answer
Invalid JSONand the connection stays usable), and every tool'spackageRootmust resolve to an existing directory inside the server working directory — outside,.., or symlink escapes are rejected as argument errors. The generated Claude Code hook parses tool input with python3/jq and a fail-closed sed fallback (#112). - Report escaping is centralized in
ReportEscapesacross all reporters;bridgesverifiespackage:flutter/services.dartprovenance against.dart_tool/package_config.jsonand recordsflutter-services-provenance-unverifiedwhen it cannot;--executetimeouts recordexecute-process-scopesince only the direct child process is killed (#114). - Performance: duplicate detection uses a rolling window hash, sealed
subtype propagation uses a precomputed inheritance index,
--sinceand impact matching resolve canonical paths in parallel, hosted/git dependencies under the pub cache no longer hash their wholelib/into the cache fingerprint (path dependencies still do), andhistorystreams the result ledger (#113).
0.12.0 #
- Added
dartograph dup, token-shingle duplicate-block detection with--min-tokensand all report formats. Findings are review candidates, not merge advice. metricsnow reports per-function cyclomatic complexity and a hot-spots ranking, gated bythresholds.complexityunder--strict.dead/deps/dupaccept--kindsfor finding-type filtering.dartograph.yamlgrewinclude/excludepath globs,retained_names/retained_filesretention roots, andthresholds.- CODEOWNERS matching now supports the full syntax (
!negation, character classes,\escapes). - Added
dartograph setup, which installs Claude Code PostToolUse hooks and merges MCP server config without overwriting existing keys. - The MCP
verify_runtool coversdup/--kinds, and aduplication-reviewprompt joins the prompt set. - VS Code extension
ictechgy.dartographis published on the Marketplace — CLI JSON reports become Problems diagnostics, withrunOnSaveand an impact check for the current file. editors/analysis_plugin/shipsdartograph_analysis_plugin(pub.dev), an analysis-server plugin that surfacesdead/dupfindings asdartograph_dead_code/dartograph_duplicate_blockdiagnostics in IDEs anddart analyze, with a// dartograph:ignorequick fix.- Added
bridges --events, an opt-in producer path that emits EventChannelreceiveBroadcastStreamlistens as bridge-facts version 2 withtransport: event-channelandkind: stream-listen. Only calls whose receiver is proven to be anEventChannelconstruction produce facts — arbitrary.listen()calls and unproven receivers are counted asunresolved-stream-listens, and dynamic channel names keep their literalchannelPrefixunderdynamic-event-channel-names.--messagesand--eventsare separate transport documents and do not combine; the defaultbridgesoutput keeps version 1 MethodChannel facts unchanged.
0.11.0 #
- Added
dartograph deps, a pubspec hygiene audit that contrasts declareddependencies/dev_dependencies/dependency_overridesagainst the observedpackage:imports and exports. It reports four finding kinds —unused-dependency,unused-dev-dependency,dev-dependency-in-lib, andundeclared-dependency— in five formats (text, json, markdown, github-actions, sarif) and exits 1 when findings exist. Tool-contract dependencies (executables,build.yamlbuilders,analysis_optionsincludes/plugins) count as used with package_config-backed evidence, while runtime/generated/asset references stay explicit limitations. Findings are a review list, never a deletion instruction. - Added
dead --closed-appandbaseline --write --closed-app. The flag drops the public-API retention root for standalone apps (Flutter apps, CLI executables), so declarations unreachable frommainare reported even whenlib/<package>.dartexports them. Reports carry aclosed-app-analysislimitation; combining it with--report-redundant-publicis a usage error (64). Never run it on a published library. - The MCP server now answers
resources/list·resources/readandprompts/list·prompts/getalongsidetools/*, declaring them ininitializecapabilities. Three static resources (dartograph://usage,dartograph://skill,dartograph://config— same bodies as the CLI help, generated skill, and config template) and three prompts (impact-precheck,dead-code-review,dependency-audit) are exposed; unknown resource URIs answer-32002.verify_runaccepts thedepscommand and aclosedAppflag, which is rejected on non-deadcommands so a silently ignored flag is never accepted. deadnow rescues sealed hierarchies: a reachable sealed declaration keeps its direct and transitive subtypes alive the way enum constants are, read from the newGraphNode.isSealedmarker.- The index additionally collects package-manifest facts (declared
dependencies, dev dependencies, dependency overrides, and
package:directive imports per package), preserves the factory functions thatbuild.yamlbuilders require asbuildRunnerroots, and preserves@JS/@staticInterop/FFI annotation targets asexternalBindingroots. The fact-cache schema moved to v4 and the index identity to v7. - Fixed a regression caused by analyzer 14's AST shape:
ClassBodynodes now sit between members and declarations, which stopped ancestor traversal and lost existing retention evidence — the walk now skips non-Declarationnodes until it reaches theCompilationUnit. - Fixed the index to recognize
include:lists,@anonymousdirectives, and unresolvedbuild.yamlimports; project-scheme dead files are now reported with unmangled sources. - CLI rejects duplicate valued options on
dead(--explain,--format,--baseline,--since,--codeowners) instead of silently applying last-win, and classifies unreadable--changedinput as a usage error instead of an analysis failure. - Reporters now escape C1 and bidi controls and fence markdown code spans
safely; the escaping policy is shared through
report_escapes.dart. - MCP silences notification-shaped request messages and honors
closedAppvalues given as strings.
0.10.0 #
-
Added
--incremental <dir>to the eleven indexing commands (graph,dead,query,compare,affected,impact,baseline,cycles,rules,metrics, anddead --explain). A per-file fact cache keyed by the file's content hash and its resolution inputs lets a run re-resolve only the changed files and the libraries that transitively import or export them; every other file reuses its cached facts. Artifacts are byte-identical to full analysis (tool/benchmark_index.dartcompares seven sha256 hashes in three conditions). The cache is an optimization, not a contract: a missing, corrupt, schema- mismatched, or unwritable cache falls back to full analysis and only the write failure is reported as a limitation. Point each project at its own directory. Measured on a synthetic 600-file package: warm (no change) 7.5x, leaf (leaf file changed) 1.9x, imported (widely imported file changed) about 1.0x — a hub edit's reverse closure is nearly the whole graph, so it is reported honestly rather than as a win -
Added a verification ledger.
--record <dir>on an analysis command appends one JSON line per run to<dir>/ledger.jsonlwith the tool version, UTC time, command, exit code, observed GitHEAD(null when it cannot be computed), the input flags, and the identifiers of the reported problems. The file is append-only: existing lines are never rewritten.--envand--dart-definevalues may be secret, so only their keys are recorded. A write interrupted mid-line is skipped on read and reported as aledger-skipped-lineslimitation, and the next append closes the truncated line before writing a fresh one.dartograph history --ledger <dir> [--commit <sha>] [--format text|json]reads it back. If the ledger cannot be written, the analysis result and exit code are unchanged and only a diagnostic goes to stderr -
Added
dead --format markdown, a table report with the same control-character policy as the other formats, including per-finding and global limitations -
Added
dead --format codeowners --codeowners <file>, which groups findings by the owners of their source paths. It implements a documented subset of the CODEOWNERS format: the last matching rule wins,*,**, and?are supported, a pattern containing/is anchored to the project root, a trailing/makes a directory rule, and a rule with no owners clears ownership instead of falling back. Paths that match no rule are grouped under(unowned) -
Documented the CI side: an
impact-precheckworkflow example now runs on pull requests, posts theimpact --format markdownreport as a PR comment (updated in place), caches the incremental facts, records the ledger, uploads SARIF, and gates on high risk. The MCP documentation gained thetools/listinput schemas, a JSON-RPC error-code table, and reproducible request/response examples
0.9.0 #
-
Added the
dartograph impactcommand for pre-change impact analysis. Exactly one seed is required:--since <git-ref>(Git changed files, with the same full-history requirement and bidirectional symlink matching asaffected),--changed <changes.json>(1–1000 project-relative paths, 1 MiB), or--symbol <symbol-id>. The report names the changed libraries and symbols, every symbol that transitively uses them (call,reference,inheritance,implements,mixin,override,import,export) with a shortest usage path and depth, the call sites into changed declarations (file, line, column), the test libraries that depend on the changed set, and a risk score (0–100,low/medium/high) with named factors (inbound-references,impact-depth,impact-breadth,public-api-surface,test-coverage,cycle-participation). Acoverageblock states what a precheck keeps from being missed: the directly changed symbol count, the transitively impacted count, related tests, andmissedWithoutPrecheck, the symbols that stay invisible when only the changed files are inspected. Formats aretext,json,markdown,github-actions, andsarif;--depthbounds the transitive walk while--limitbounds only what is reported (counts, risk, and exit code are unaffected, and each truncated list says so), and--fail-on <none|low|medium|high>exits 1 when the overall risk reaches the threshold (defaultnonealways exits 0). A--symbolseed absent from the graph is answered withknown: falseand exit 64, and the report never claims that an unlisted declaration is unaffected -
Added the
dartograph mcpcommand, a Model Context Protocol server over stdio (JSON-RPC 2.0, protocol version2024-11-05;initialize,ping,tools/list,tools/call, andnotifications/*are handled, with-32601for unknown methods,-32700for malformed JSON, and-32602for invalid parameters while the server keeps running). Three tools are exposed:impact_query(theimpact --format jsondocument;since/changed/symbol, exactly one),dependency_query(query/query --batch;symbol/batch, exactly one), andverify_run(dead/cycles/rules/metricswith the raw output and exit code,isError: truefor analysis failures and usage errors). Every tool reuses the samerunDartographexecution path as the CLI, so tool result schemas and exit codes cannot drift fromdartographitself. stdout carries only JSON-RPC and diagnostics go to stderr; the tools are read-only, and the temporary files written forchanged/batcharrays are removed when the call returns -
Added the
dartograph runtimecommand, which finds inputs the static import graph cannot see and verifies them against an environment. Detected facts fall into five categories:env(environment variables and--dart-define),dynamicLoad(Isolate.spawnUri,Process.run/start,DynamicLibrary.open,dart:mirrors,Function.apply),config(configuration files and paths),asset(pubspec.yamlflutter.assetsdeclarations androotBundle/Image.asset/AssetImage), andexternal(http(s) destinations). Each fact is judgedpresent,defaulted, ormissingin the given environment, or leftunverifiedwith a reason when it cannot be decided statically or probed; unmet, undecided, and external counts sum into a risk score (0–100,low/medium/high). Verification is on by default, and--verifystates it explicitly while--no-verifyonly detects without judging (no risk score).--env KEY=VALUEand--dart-define KEY=VALUErepeat (last value wins) and never print their values; providing either channel makes verification hermetic — the process environment is ignored and reported as anenvironment-sourcelimitation — and the two channels do not satisfy each other.--execute <dart-entrypoint>runsdart run <entrypoint>from the package root with the Dart SDK onPATH, applies--envvalues over the inherited environment, and records the exit code and a stderr summary as execution evidence; a failed run adds anexecution-failedrisk factor rather than replacing the report.--formatistext,json,markdown,github-actions, orsarif,--limitbounds reported items only, and--fail-on <none|low|medium|high>exits 1 at the threshold -
Fixed
runtime --executein native executables to launch the Dart SDK on PATH instead of recursively launching dartograph. Installation contracts now check that the entrypoint actually produces an execution witness. -
Apply the runtime execution deadline to output collection as well as process exit, including when a descendant keeps inherited output pipes open.
-
Added opt-in
source_packagesconfiguration for local path dependency source. Declared package roots must be project-relative, canonical non-symlink directories withpubspec.yamlandlib/; generated/cache and duplicate roots fail closed. The packagelib/is included with its existingpackage:identities, while the default analysis scope remains unchanged. Configuration and package contents invalidate the analysis cache. -
Added the development-source-only
bridges --messages --format jsonproducer for observed FlutterBasicMessageChannel.sendcalls. It emits bridge-facts v2 withtransport: "basic-message-channel"andkind: "message-send", while keeping channel construction and MethodChannel method facts separate. Dynamic names preserve their source expression; an optionalchannelPrefixis emitted only for an AST-proven decoded, non-empty leading literal in a string interpolation. Prefixes are candidate evidence, not complete runtime address or instance identity, and this producer is new in0.9.0.
0.8.0 #
-
Added a runnable
example/main.dartdemonstrating the public library API: constructing aCodeGraph, snapshotting it, querying dead declarations through aSymbolQuerySession, and serializing findings withtoJson -
Widened the validated
analyzerdependency range to>=14.3.0 <15.0.0(14.4.x re-validated perdoc/DECISION-analyzer.md; the full test suite compiles and passes against 14.4.0). Cache keys already incorporate the resolved package configuration, so analyzer upgrades invalidate cached analyses naturally -
Added the
dartograph init [--force] [<package-root>]command, which writes a commenteddartograph.yamlconfiguration template to the project root (parity with cartograph'sinit). The template advertises only the implemented schema (entry_points). An existing file blocks generation with exit 64;--forceoverwrites it. The write is an atomic replacement — a symlink at the target path is replaced as a link, never followed -
Added actionable CLI diagnostics and expanded
--helpcontract documentation:skill --installsuccess and conflict messages now name the installed path (<dir>/dartograph/SKILL.md);rulesconfiguration failures are split into unreadable (echoing the user-supplied--configvalue) and invalid (parser detail, no path); unknown report format / graph level / graph format one-line errors now list the accepted values;initwarns on stderr when the target directory has nopubspec.yaml(exit code stays 0); and--helpdocuments the skill install path, the replace-symlink-as-link policy, and the exit-code contract (dead --explainof an unreachable target exits 1; a query/--explain target absent from the graph exits 64) -
Security hardening:
skill --installno longer writes through a symlink at the destination — previouslyFile.writeAsStringfollowed links and could clobber a file outside an untrusted checkout (with no--forceneeded when the link dangled). init, skill, and baseline writes now share one atomic write boundary with hard-to-predict temp names (PID plus a suffix drawn from a cryptographically secure generator) and exclusive creation, so a pre-planted entry at the temp path fails closed instead of being truncated or followed. Narrow breaking change: a dangling symlink at<dir>/dartograph/SKILL.mdnow requires--force, matchinginit— without--force, any existing file or link at that path is refused with exit 64 -
Narrow breaking change: repository-provided YAML configuration files (
dartograph.yaml,pubspec.yaml, layers.yaml passed torules --config) larger than 1 MiB are now rejected with a static path-free error before parsing instead of being handed to the YAML parser (fail-closed resource bound; real configs are orders of magnitude smaller). Each read site keeps its existing failure contract (analysis exit 2, or the workspace-detection limitation fallback) -
Performance: regular expressions are no longer recompiled inside hot loops — the operator-name pattern in
dead --report-redundant-public, the fact-cache key pattern, the bridge fact control-character pattern, and the generated sibling suffix pattern are now built once. Per-source limitation filtering is memoized in the dead-declaration, dead-file, and redundant-public finding loops. For inputs where the new size cap and destination refusals do not trigger, analysis output remains byte-for-byte identical to 0.7.0 (verified by artifact hash equality)
0.7.0 #
-
Added
graph --format anonfor privacy-preserving graph export (parity with dependency-cruiser'sanonreporter). Package-relative paths and file paths are anonymized using deterministic, injective identifiers (s0,s1, ...) while preserving graph topology, file extensions, and a whitelist of standard Dart idiomatic vocabulary. Analyzer limitation strings are sanitized in a single alternation pass to prevent double-replacement leaks -
Added
dead --report-redundant-publicto identify public declarations that are never referenced outside their defining library (parity with Periphery's redundant public accessibility check). Follows the informational contract of--report-test-only(findings emitted asinfo, command exits with code 0). Does not combine with--explain,--baseline, or--report-test-only(usage 64);--sinceand machine-readable output formats are allowed. Conservative exclusions preserve retention roots, enum constants, override implementers, operators, and private container members -
Added architectural zone classification to
metricsJSON output (parity with cartograph'sMetricsZone). Each component metric entry now includes azonefield (main-sequence,zone-of-pain,zone-of-uselessness, orisolated). The boundary aligns with--strictthreshold calculations -
In
graph --format dot, vertices participating in circular dependencies are now highlighted in red (color="#d9383a",fontcolor="#d9383a") (parity with madge). Detected via Tarjan's SCC algorithm; DOT output for acyclic graphs remains byte-identical -
GraphNodenow carries an explicitisLibraryboolean flag instead of relying on.dart::string heuristics in HTML exports and projections. Libraries whose file names contain::are now correctly classified. Analysis cache serialization schema version incremented tov3(cache identity unchanged) for automatic re-analysis of prior caches without changing serialized JSON, DOT, or Mermaid outputs -
Fixed grammatical agreement in
bridgeslimitations across all three diagnostic families (unscanned-*plural nouns for N ≥ 2 and dynamic-* singular verb agreement for N = 1)
0.6.0 #
-
Breaking change (library API): the public library surface (
package:dartograph/dartograph.dart) was trimmed to what is actually supported. CLI behavior and exit codes are unchanged.querySymbolwas removed — a one-shot wrapper aroundSymbolQuerySessionused only bytool/benchmarks. Construct aSymbolQuerySessionand callqueryinsteadCodeGraph.usageEdgesFromwas removed (no product caller; filterCodeGraph.edgesinstead)SymbolQuerySession.analysis(aReachabilityResultfield) is no longer public. The session now exposesdeadDeclarations(an unmodifiableList<DeadFinding>) for thequery --baselineflow, so no public member references an unexported type. Reachability questions are answered bySymbolQuerySession.query, whose document carries each symbol's reachability state.ReachabilityResult/ReachabilityExplanationstay internal;DeadFindingis now exported
-
bridgespub workspace detection now validates membership. A package whose pubspec declaresresolution: workspacejoins the nearest ancestorworkspace:root when that root plausibly lists it — explicit paths are matched after URL normalization, while glob entries and non-listworkspace:values are conservatively accepted. Only a well-formed explicit-path list that omits the package falls back to the scan root, with a newpub-workspace-member-not-listedlimitation so a misconfigured workspace never silently skews the isthmus join basis -
SARIF output no longer corrupts already-absolute source URIs. A dead finding whose source is
file:(an out-of-root path) orpackage:(a dependency) is passed through unchanged instead of being split on/and re-encoded (which turnedfile:///a.dartintofile%3A///a.dart). Project-relative sources keep the existing per-segment encoding (backslash- and percent-safe) -
HTML graph output now classifies a library whose file name contains
::correctly, using the.dart::declaration boundary instead of any::. Such a library is no longer shown as a member, and its display name is no longer truncated at the first:: -
README revised for clarity (English original and Korean twin); no behavior change
0.5.0 #
-
bridgesgained--project <shared-root>and pub workspace auto-detection (isthmus monorepo join request #38; GRAPH-EXCHANGE delegates the shared-root declaration to producer options)--projectkeeps the scan on the positional package root while the document'sprojectfield andlocation.pathbecome relative to the shared root (POSIX realpath; it must be an existing directory containing the package root, else usage 64). Sibling packages of a pub monorepo (e.g. a*_platform_interfaceholding the MethodChannel and the plugin package holding the native side) can now emit the exact sameprojectstring the isthmus strict-equality join requires — no hand-rewriting documents, which breaks provenance- A package whose pubspec declares
resolution: workspacepicks up its pub workspace root (nearest ancestor pubspec with aworkspace:key — the Melos definition) without--project. Precedence:--project> workspace detection > scan root. Detection failures fall back to the scan root and reportpub-workspace-root-not-found/pub-workspace-pubspec-unparsedlimitations so a skewed join basis is never silent - Without a workspace declaration or
--projectthe output is byte-identical to before (project = realpath of the scan root, paths relative to it) — existing bridge goldens pass unmodified - The bridges control-character rejection message now reads "a fact value
or source path contains control characters" — message-only, no behavior
change: source paths have been validated since 0.3.0, and empty names are
skipped into the
empty-bridge-nameslimitation rather than throwing, so the old "is empty" attribution was unreachable/wrong
-
Indexing got measurably faster with byte-identical output (audit P1/P2/P9/P10; measured with the new
tool/benchmark_index.dartA/B harness — sha256 of graph, dead, query, retention, and test-only outputs identical before/after):- Element-to-ID resolution is memoized per relationship-collector pass; it used to recompute path normalization and name chains for every identifier visit
CodeGraph.nodes/edgesread views are cached and invalidated on mutation instead of re-sorting on every access;_addPublicApiRootshoists its node-id list out of the per-export loop- The edge comparator is shared between
CodeGraphandGraphSnapshot(single determinism implementation), pubspec.yaml is read once per index, and declaration source paths are computed once per declaration - Synthetic benchmark (600 files, 4,923 nodes / 14,726 edges, Dart 3.13.3, macos_arm64, min of 3 cold runs): index 1456 ms -> 1053 ms (-28%). Machine- specific; relative comparison only, not an SLA
-
Two more measured hot-path fixes with byte-identical output (audit P4/P8; harness now also times
rulesevaluation and hashes its violations):LayerRuleEvaluatorcaches compiled glob RegExps per pattern instead of recompiling for every node × layer × pattern (first-match assignment scans all patterns for unmatched nodes). Benchmark: rules evaluation on 4,923 nodes 10.8 ms -> 3.0 ms (-72%), violations hash identicaldead --sinceresolves each unique finding source's symlink once instead of once per finding (findings of one file shared the syscall before)
-
Reachability/query hot loops got indexed with byte-identical output (audit P3/P5/P6; same A/B harness, all six artifact hashes identical):
ReachabilityResultgainsisReachable(set lookup) andreachableMemberOf(dot-prefix witness index built once, deterministic first-in-sorted-order semantics preserved) —querybatches andcomparelosses no longer run a linear scan per query/loss. Synthetic benchmark: 100-query batch on 4,923 nodes 10.7 ms -> 1.7 ms (-84%)- The double sort of reachable ids per
analyzeis gone (5.7 ms -> 4.7 ms on the benchmark), andcompareGraphshoists its limitation dedup+sort out of the per-loss loop
0.4.1 #
--since/affectedGit change matching is now bidirectional for symlinked sources: both the link path itself (the link file changed or was retargeted) and the resolved physical target (the target changed) are matched against the changed set — previously only the resolved path was compared, so a changed link file silently fell out of scope (measured)- A
dartograph.yamldeclaringentry_pointsnow reports anentry-points: main retention roots narrowed to N declared build target(s)limitation — a configuration added by a pull request can no longer hide dead code indistinguishably from a clean repository - SECURITY.md documents the symlink ingestion channel: dartograph follows links inside the analysis tree like the analyzer does, so a repository that plants links can pull same-user files from outside itself into its graph output and CI artifacts
- Output fidelity fixes from the audit (all additive or misinformation-only changes)
dead --format jsongains areportfield (dead/test-only) so a stored artifact is machine-classifiable without the exit code — the four report formats are now lossless-symmetricdead --format github-actionsemits a::notice ... suppressed by baselineline when the baseline suppressed findings (the other three formats already reported the count; zero stays byte-identical)graph --format jsonnodes carryisEnumConstant: truefor enum constants (conditional-field convention likeline/column; the cache document already had it — the public exchange format can now reproduce the enum-constant retention decision)- SARIF file findings no longer invent a
regionof line 1 column 1 —regionis optional in SARIF and fabricating position evidence violates the evidence contract (declaration findings keep their real region) - The determinism contract now names its second declared exception: the
generated-code-stalenesslimitation is an mtime observation (git does not preserve mtimes, so its presence can differ across fresh clones; findings, nodes, and edges are unaffected) — documented in USAGE and lib/AGENTS.md
- The analysis cache key now hashes every
.dartfile under the package root (excluding.dart_tool/.git/build), not only the five standard source directories — the analyzer also reads outside the standard directories through the import closure (e.g.tool/), and a key that missed them reused a stale analysis after such a file changed (reproduced: resolution limitations vanished). Hidden directories (e.g..fvmtoolchain links) are pruned from the walk so key computation cannot balloon into hashing a whole Flutter SDK, and nested-package discovery widens to pubspecs outside the standard directories. Relative imports that leave the root remain a documented coverage boundary.toolVersionis part of the cache identity, so upgrading to 0.4.1 invalidates existing caches automatically (one reanalysis) - Hardened the CLI error boundary:
Errors (TypeError, RangeError, …) escaping from analyzer/yaml internals are contained at the command boundary as an analysis failure (exit 2) — no more stack traces echoing internal paths or an undocumented exit 255. This also closes per-command catch asymmetries (e.g._runBaselinehad noon ArgumentError; compare/query leaked ArgumentErrors) - The bridges control-character policy rejection now reports "Bridges extraction failed: …" instead of "unable to index the package" (correct attribution — the rejection is an extraction policy, not an indexing failure)
- Non-UTF8
gitoutput (possible filenames on Linux) folds into the ChangedFilesException diagnosis ("Changed files could not be computed…") instead of surfacing as a misattributed indexing failure - Unified the control-character and injection policy across every human and CI
output surface (audit follow-up: newline-bearing filenames could cut a Mermaid
label into two statements, forge text diagnostic lines, and pass ESC/bidi
characters through to GitHub Actions logs)
- Mermaid: CR·LF become the documented entity codes (
#13;·#10;) so labels always stay on one physical line; a literal#10;in the source round-trips thanks to the leading#→#35;substitution - DOT: raw CR is now escaped like LF (a display-level line break; statement structure is preserved)
- text reports: C0·DEL characters in paths, ids, evidence, and limitations
become visible escapes (
\n·\r·\t·\xNN) — a secondpath:line:col:diagnostic line can no longer be forged and terminal ANSI injection is neutralized - GitHub Actions: percent encoding extends beyond the spec minimum (
%, CR, LF,:·,in properties) to C0·DEL·C1, U+2028/2029, and the bidi controls (U+202A–202E·U+2066–2069) — same%0D/%0Aconvention, normal inputs unchanged - SARIF: the artifact
uriis built from per-path-segment encoding instead ofUri(path:), which silently corruptedback\slash.dartintoback/slash.dartand misattributed a literal%41.dartasA.dart - The policy table is documented on the export module
(
lib/src/export/graph_exporter.dart). Only pathological inputs change bytes; normal paths and ids are byte-identical
- Mermaid: CR·LF become the documented entity codes (
0.4.0 #
- Added
// dartograph:ignoreinline comments (absorbing Periphery's comment command)- When the body of a line comment above a declaration starts with the marker
(doc comments and block comments are not directives), the declaration's dead report
is suppressed. Prose that merely mentions the marker does not trigger suppression, and a
reason can follow as in
// dartograph:ignore — reason. A trailing comment at the end of a line (void foo() {} // dartograph:ignore) is not interpreted as suppressing the next declaration (misattribution guard). For variables and fields, the marker on the enclosing declaration applies - A suppressed declaration becomes a retention root with
retentionReason: inlineIgnore, sodead --explain,query, and compare answer with that evidence. The user directive takes precedence over other retention reasons. Because it is a reachability root, what the suppressed declaration references also disappears from the report — use a baseline to suppress a single finding. Only the declaration itself is suppressed; it does not propagate to members or files - Retention-root extraction semantics changed, so the analysis cache identity is bumped to v5. Old caches are reanalyzed automatically (serialization format unchanged)
- When the body of a line comment above a declaration starts with the marker
(doc comments and block comments are not directives), the declaration's dead report
is suppressed. Prose that merely mentions the marker does not trigger suppression, and a
reason can follow as in
- Added
--level <file|type|symbol>and--collapse <n>tograph(absorbing cartograph'sgraph --leveland dependency-cruiser's--collapse)--level filefolds declarations into their libraries,typefolds members into top-level declaration containers, andsymbol(the default) draws the graph as is. Self-loops created by folding are dropped and edges are deduplicated after representative substitution. The default output is byte-for-byte identical to before- dartograph analyzes a single package, so cartograph's module resolution does not exist
(folding to the package yields one vertex) —
fileis the coarsest level --collapse <n>summarizes the file-level graph to the leading n path segments (project:lib/src/a.dartbecomesproject:lib/srcat n=2). Folder vertices are aggregates without location fields. Combining with a level other than--level file, a missing value, duplicates, an unknown resolution, values below 1, and non-integers are usage errors (64)
- Added
htmltograph --format(absorbing cartograph'sgraph --format html)- A single self-contained HTML file with no external CDN, script, or font references at
all. It opens on air-gapped networks and in CI artifacts; the graph facts ride in a
<script type="application/json">payload and render with an inline canvas force-directed layout, search, pan, and zoom - Above 400 vertices the most connected vertices are kept first, and the truncation is
stated on the page and in the payload (
truncatedFrom). Use--format dotfor the full graph <in the payload becomes\u003c(a valid JSON escape) so dartograph's own node IDs like<no-library>cannot break script-tag tokenization. Limitations ride both in a collapsible header list and in the payload
- A single self-contained HTML file with no external CDN, script, or font references at
all. It opens on air-gapped networks and in CI artifacts; the graph facts ride in a
- Added the
affected <git-ref> <package-root>command (absorbing dependency-cruiser's--affected)- Seeds the libraries to which files changed since a Git reference (commit, branch, tag,
HEAD~1, …) are attributed, walks import/export edges in reverse, and answers with the transitively dependent libraries as JSON. Changes to part files are attributed to the host library - Each affected library carries
pathanddepthevidence — the shortest dependency chain to the nearest changed library — andchanged(seeds) andaffected(dependents) do not overlap. The impact radius is a library (file) level observation - Changed Dart files that belong to no analyzed library are reported as the
changed-dart-files-without-librarylimitation. Git failures get the same diagnosis and exit code 2 as--since; a successful report exits 0 regardless of the impact count
- Seeds the libraries to which files changed since a Git reference (commit, branch, tag,
- Added
dead --report-test-only(cartograph parity)- Recomputes reachability without the test-directory (
test/,integration_test/, …) retention roots and selects production declarations reached only from tests. This is not dead code — it is the observation that "tests are the only caller" - Reported at
infoseverity (textinfo:, github-actions::notice, sarifnote, ruleIdtest-only-declaration), and the exit code is 0 even with findings (it never fails the build). Declarations inside test directories and@visibleForTestingproduction declarations are conservatively excluded - Does not combine with
--explain(a single-target query) or--baseline(which suppresses dead findings) — usage 64;--sinceand--formatare allowed
- Recomputes reachability without the test-directory (
- Added
cycles --explain <symbol-id>andrules --explain <symbol-id>(cartograph evidence parity)cycles --explainemits as JSON the cycles a vertex takes part in as a strongly connected component and each cycle'sbreakCandidate(candidate edge to break). A vertex belongs to at most one strongly connected component, so the answer is 0 or 1 cyclesrules --explainemits as JSON the layer the vertex is assigned to, thematchedPatternandmatchedCandidatethat decided the assignment, and therulesstarting from that layer. With no matching layer those fields are null/empty lists (--configis still required)- Both
--explainforms are single-vertex queries and do not combine with--strict(usage 64). An ID absent from the graph is answered withknown: falseand exit code 64; a known ID exits 0 regardless of participation
- Added
--depth <n>and--limit <n>toquery(cartograph SymbolQueryDocument parity)--depth(default 1) follows usage relations (usedBy,dependsOn) with BFS up to n hops; each neighbor'sdepthfield says how many steps it is from the queried symbol. All edge kinds reaching one neighbor ride inedges, and a neighbor reachable through several paths is reported once at its shortest depth--limitcaps the number of neighbors per direction; when it omits neighbors, that direction'struncatedbecomestrueand omitted neighbors are not expanded further. Containment (members,declaredIn) is always one hop- The defaults (depth 1, no limit) preserve the previous output, with one narrow
breaking change: usage edges pointing at a symbol itself (recursion) are excluded
from that symbol's own neighborhood, matching cartograph (the old 1-hop walk included
them). Values below 1, non-integers, missing values, and duplicate flags are usage
errors (64). Combines with
--batchand--baseline
- Fixed a false positive that reported
operatordeclarations in use as dead- Operator syntax (
a + b,a[i],-a,a++,a += b) resolves through tokens rather than identifiers, so no usage edges were created; operator declarations on classes and extension types were wrongly reported unreachable while being consumed - Resolved operators are now recorded as
calledges. Thea[i] = vwrite keeps its existing reference path, and the[]/[]=that compound assignment and increment (m[i] += v,m[i]++) read and write through are recorded as operator calls too. Built-in operators (dart:core) have no nodes and gain no edges; unused operators and read operators consumed only by writes keep being reported (bidirectional corpus regression) - Extraction semantics changed, so the analysis cache identity is bumped to v4. Old caches are reanalyzed automatically (serialization format unchanged)
- Operator syntax (
- Input error messages are now distinguished per cause so they never point at the wrong one
- A missing baseline file yields "Baseline is invalid: create it with dartograph baseline --write" instead of "unable to index the package"
- A missing or invalid
rules --configfile yields "Analysis failed: unable to read the rules configuration." (distinguished from indexing failure; theAnalysis failed:prefix is kept) - A
baseline --writewrite failure (destination creation, permissions) yields "Baseline write failed: unable to write the baseline file." instead of "unable to index the package" (indexing already succeeded)
- Dead-file findings for percent-encoded filenames keep their file-level limitations
%20and friends preserved byUri.pathare decoded so they match the source limitations the analyzer built from real paths; previously those files' limitations silently vanished from findings
- Mermaid output escapes
<,>,&,",\, and#in its own node IDs with HTML entity codes- Fixes Mermaid mistaking
<no-library>and<unnamed-extension@...>for HTML tags, caused by reusing the DOT backslash escape - Quotes cut a quoted string in half and break label structure, so they become the
entity codes Mermaid documents (
#quot;, backslash#92;,#itself#35;)
- Fixes Mermaid mistaking
--helpnow states that--explainrequires--format jsonand does not combine with--baselineor--since- The agent
skilloutput gains anaffectedentry andinlineIgnoreevidence wording (updated alongside the new features)
0.3.0 #
- Fixed unreachable false positives for enum constants consumed only through
.values- Enum constants have only enum→constant
memberedges, which do not count as usage, and container rescue runs member→container only, so constants could not be saved. With consumption likeStatus.valuesthat never references individual constants, even constants of a reachable enum were wrongly reporteddead - A reachable enum now retains its constants, and
explainreturns theretained by its reachable enumevidence together with a real path to the enum. If the enum itself is unreachable, its constants keep being reported - Node serialization gains the enum-constant flag, bumping the cache schema to v2. Old caches are rejected at decode and reanalyzed
- Enum constants have only enum→constant
- Fixed silent-miss and total-failure defects in Flutter channel fact extraction
- When
static final _channel = MethodChannel(...)was declared after its uses inside a class or other declaration body, method-invoke facts were dropped entirely and downgraded tounresolved-receiver-invocationscounts without position or symbol. Declaration-body fields are now prescanned, so resolution no longer depends on declaration order; shadowing by a same-named top-level channel is still respected - A single empty channel or method name such as
MethodChannel('')used to fail the wholebridgesdocument without file or line information. Only that fact is now skipped and aggregated into theempty-bridge-nameslimitation while the remaining facts are emitted normally. Names containing control characters are still rejected entirely
- When
- Fixed
dead --sincemissing changed files underdiff.relative=trueand in sub-packagesgit diffprints cwd-relative paths, which were joined with the repository root, misaligning paths and silently dropping all findings (exit 0).gitnow runs with-c diff.relative=falseso paths are always repository-root relative
- Fixed option-shaped values being accepted as paths
baseline --write --force .actually created a file named--forceand reported success; it is now rejected as a usage error (64) before indexing and writing- Calls with a missing value now yield usage errors (64) instead of analysis failures
(2): the package root of
query,bridges, andgraph, and the values ofrules --configanddead --baseline/--since compareonly checked for--, so an existing short option likecompare -h .was accepted as a path; single-dash values are rejected too- Narrow breaking change: calls that used to work by passing a path starting with
-(e.g.baseline --write -b.json .) now exit 64. Pass./-nameinstead.bridgeskeeps the existing--escape. This was an undocumented implicit allowance, andquery --batchhad the same restriction from the start
- Fixed
dead --explainasserting a container retained by a member was unreachableexplainanswered "unreachable from all retention roots" with exit code 1 for declarations thatdeadin the same run excluded from findings- It now returns the
retained by a reachable memberevidence with the witness member and a real path to that member, and exits 0 query'sretainedByMemberstate andcompare's witness notation are unchanged
- Symlinked Dart sources are included in analysis-cache inputs and the bridge scan
- The analyzer follows both file and directory links, but they were missing from the input list, so editing a link target left the cache key unchanged and returned a stale graph; fixed
- Platform-channel facts of linked sources, missed for the same reason, are now extracted too
- Directory links record the already-followed real paths so cycles do not loop forever
- Broken links keep being excluded (there is no target)
- Added the
dartograph.yamlentry_pointsoption to declare real build targets and narrowmainretention roots- Without it, the previous conservative policy (every
mainunderlib/,bin/,example/) is kept - Empty documents and comment-only documents declare no entry points and are treated the same (default policy); only a non-empty non-mapping document is rejected
- Empty, absolute, outside-root, non-string, out-of-scope (not under
lib/,bin/,example/), missing, or non-.dartpaths are reported as analysis failures instead of being silently ignored - An entry point that exists but has no
mainis reported as theconfigured-entry-point-without-mainlimitation - Retention-root semantics changed, so the analysis-cache identity is bumped to v3 and the configuration is included in the cache key
- Without it, the previous conservative policy (every
0.2.0 #
- Per-source limitations (analysis errors, unresolved invocations, conditional configurations) attached to findings
query --batchand the library-facingSymbolQuerySession, sharing one index and reachability computationcompare, explaining graph changes between two checkouts and the reachability paths that disappeared- Supported Dart declaration names added to bridge facts, with isthmus round-trip evidence verified in both directions
- Source-mutation regression evaluation and query-session performance measurement tooling
0.1.1 #
- UTC millisecond generation timestamps and UTF-8 byte positions per isthmus GRAPH-EXCHANGE v1
- MethodChannel extraction following Flutter services import provenance and lexical scope
- cascade and invokeListMethod/invokeMapMethod support, dynamic-name facts, and unattributed/invalid-call limitations
- EventChannel, BasicMessageChannel, conditional imports, and re-exports reported as limitations instead of false facts
- Usage through Flutter services re-exports is preserved toward the documented miss instead of being guessed
- explain's not-found and file evidence hardened; analyzer identity, generated code, and nested-dependency cache decisions hardened
- Invalid CLI calls distinguished from Git failures; option-shaped skill paths rejected
0.1.0 #
- Deterministic symbol- and file-level dependency graphs on Dart analyzer 14.3.0
dead,query,cycles,rules, andmetricswith evidence and limitations- DOT, Mermaid, JSON, text, GitHub Actions, and SARIF output
--sincecombining a baseline with a Git change scope- Corruption-tolerant persistent fact cache keyed by contents and analyzer identity
- Retention rules: package-barrel public API, multiple mains, overrides, generated code, tests, plugins
bridges, exporting Flutter platform-channel exchange factsskill, printing and installing safety guidance for agents- Exit codes
0/1/2/64, a false-positive corpus, and a 90% line-coverage gate - Retention-root evidence for large projects bounded by count, a 20-item sample, and a truncation flag
dartograph is MIT licensed and permanently free, commercial use included. It provides no deletion verdicts and no automatic deletion.