History of notable changes to agda-deps. Reverse-chronological. For
runnable recipes see Examples.md; for forward-looking
work see TODO.md; for deferred / refused ideas see
Backlog.md.
The wiki-backlinks view named its navigation stack history, which
at global script scope aliases the read-only window.history — so the
first navigate(…, push) threw history.slice is not a function and
broke back/forward. Renamed the stack to navHistory. HTML-only.
Fixes the anonymous-module blind spot: defs inside where blocks and
module _ (…) where sections produced false module-level and provenance
results. Agda desugars both into anonymous sub-modules (Mod._) and
lifts enclosing variables into each def's defType, so the dependency
edges were already correct — only naming/attribution/provenance was wrong.
nodeKeylifts anonymous segments (liftAnonSegments):Mod._.helper@15↦Mod.helper@15,Mod._._.deep↦Mod.deep. The@<line>disambiguator is preserved; mixfix names (_+_) are untouched.nodeKeyVersionbumped 2 → 3.moduleKeyre-homes module attribution to the nearest named ancestor; every QName→module-string site routes through it, killing phantomMod._nodes and their falseMod ⇄ Mod._cycles.- Provenance
where→module-local: the tag describes the target (an anonymous-module-local helper) rather than claiming an unrecoverable source-ownership relation. Packed int encoding unchanged (still2); expanded string + schema enum updated.
where and section are represented identically post-scope-check, so
they are not distinguished. Cold golden regenerated. New regression
test/AnonSection.agda; test/Collision.agda still locks the where case.
Memory pass on the producer's rebuild path. The dominant peak-RSS term is Agda's own interface-load floor (irreducible in a whole-program backend); of the removable remainder, copying-GC slack was the largest. Output is byte-identical.
-rtsopts -with-rtsopts=-F1.2(agda-deps.cabal). The binary had no-rtsopts, so+RTS/GHCRTSwere ignored.-F1.2caps old-gen heap growth at 1.2× live (default 2.0×): ~11% lower peak RSS at no measurable wall cost, no hard ceiling.-rtsoptslets callers override.- Strict
_deps/_stateinADDef— the two fields that lacked!; a lazy_statethunk closed over the whole AgdaDefinition. - Force
liveModules(Backend.hs) — a lazy[String]pinned the entiredefMapalive through the render in non---incrementalruns.
--incremental is a wall-time win, not a memory one.
Packed graph.json is ~5× smaller than expanded but omitted every
per-definition analytical field. --packed-analytical adds them so
consumers no longer have to choose size or fidelity.
- New arrays parallel to
defs.names:defs.kinds(Int8),defs.lines(Int32,-1=unknown),defs.access(Int8),defs.types(under--with-signatures), CSR-packed subterm offsets/hashes/depths (under--with-term-hashes). No topology change. accessis 3-valued (0=unknown/absent,1=public,2=private): expanded omitsaccessfor QNames with no localADDef, and0round-trips to that omission (line→-1,type→null likewise), so the two forms match exactly.- Shared per-QName lookups guarantee parity between the expanded emitter and the packed arrays — don't inline them back per-form.
- Off by default; default packed output stays byte-identical. Only
meaningful with
--json-mode=packed. Gated byschema/packed_analytical_check.py(run both sides cold).
2026-06-13 — agda-deps — incremental serialise (P2), cache GC + --cache-dir, re-export-hub externals
New AgdaDeps.SerialiseCache (plain-text manifest; no CPP) cuts
serialise+write on a rebuild, on top of P1's per-definition walk cut.
- Monolithic output (
deps.json/inlinedeps.html) can't be patched cheaply, so the win is the no-op rebuild: when nothing recompiled (recompiledRef) and theoutputToken(module set + output-affecting options + build identity +nodeKeyVersion) matches, generation and write are both skipped. Both guards are required — the token alone misses a recompiled body,recompiledRefalone misses a toggled rendering option. - Lazy per-module files carry a content epoch computed from the structured inputs (no base64), so a skipped file never forces its content thunk. Adding/removing a def shifts global indices, so many epochs change (correct if not minimal).
- Profiling shows the warm-rebuild floor is Agda's interface load, which a backend cannot avoid; this helps signature-/source-heavy output and no-op rebuilds, not the floor.
- Gated on
--incremental; default output path byte-identical.
- GC: after an
--incrementalrun,*.fragfiles for modules no longer in the graph are pruned (gcFragments); only*.fragtouched. --cache-dir=PATH(CLI + YAML) overrides the default<out-dir>/.agda-deps-cachefor fragments and the serialise manifest.
A stdlib module that only open … publics names contributes no QName of
its own and slipped past --no-externals. The re-export host + source
module names (collectReExports) are now pooled into the classification
input. Additive: golden unchanged.
On a real broken corpus the partial pass died with a bare exit 120 and
no deps.json. Root cause: exit 120 is Agda's __IMPOSSIBLE__, a GHC
exception (not a TCErr), invisible to every catchError guard; and
TCM's catchError rolls back TCState, so re-merging only interface
signatures left reification hitting a missing-builtin __IMPOSSIBLE__.
mergeIfaceStatenow rebuilds the import state as Agda's ownmergeInterfacedoes: signature + builtins (with per-primitive rebinds) + remote metas + pattern synonyms + display forms.catchAllTCM(catches TCErr and GHC exceptions; re-throwsExitCode/async) guards every stage — per definition, per module, per interface merge;preCompile/postCompilefailures print a stage-named diagnostic before re-throwing.- The partial pass no longer claims an entry module — it passed
IsMainto every module, soentryModulerecorded whichever ran last. - Fixture + CI step
test-keepgoing/(kept outsidetest/): entry imports one healthy and one failing module; CI assertsdeps.jsonis produced withfailedModules == ["Broken"].
P1 of the incremental-rebuild design: the dominant rebuild cost is the
per-definition backend walk, re-run for every module even when nothing
changed. AgdaDeps.FragmentCache caches, per module, what postModuleAD
returns plus the module's contributions to the two compile-time
side-channels (ignored edges, instance-method providers).
- The slices are exact before/after deltas snapshotted in
ModuleEnv— aSkipped module never runscompileDef, so without them contraction silently loses edges through its helpers. A name-prefix filter is wrong: it misses defs Agda homes in anonymous modules at prefixless QNames. - Key:
(fragment format version, content-option fingerprint, iFullHash, nodeKeyVersion).iFullHashfolds in transitive imported hashes, invalidating exactly the affected cone; rendering-only options are excluded. - Flow:
moduleSetupreturnsSkipon a hit.postModuleADwrites imported modules unconditionally, the main module only from a fresh check (its output is enriched by thegetSignaturedead-private recovery a warm load can't see), so a fragment hit serves the complete main-module variant even warm. - Serialisation via Agda's
EmbPrj, soQNames (NameIds, ranges) round-trip exactly. The byte layer is only exposed by Agda ≥ 2.9; on 2.8 the flag warns + no-ops. --incremental/ YAMLincremental: true; disabled under--keep-going. All failure modes degrade to recompute, never abort. CI: cold-write + warm-hit must be byte-identical and match the golden.
The committed golden had been generated warm, freezing the degraded
main-module variant (missing Test.Int-0-class pattern-helper
edges/kinds/types — the "warm-.agdai edge loss"). The golden is now the
cold (complete) variant, and CI clears .agdai before the runs that feed
the golden check. The warm loss is main-module-only: imported modules'
emission is a pure function of their pruned interface.
toExpandedGraphextracted frombuildExpandedJson(now a thin validate-then-encode wrapper).Wire.validateExpandedasserts the structural invariants JSON Schema can't express (provenance/subterm array lengths, edge endpoints name a definition);buildExpandedJsonerrors on a violation.- Golden snapshot guard:
test/golden/expanded.golden.json+schema/golden_check.pycatch content regressions; the normaliser strips build/layout/path-volatile fields so it's portable. New CI step. - Output byte-identical (refactor + new guards).
The expanded graph.json is now encoded from the same
AgdaDeps.Backend.Wire field tables that generate the schema, closing
the drift gap by construction.
buildExpandedJsonbuilds anExpandedGraphand callsencodeExpanded(=encodeObject expandedFields). Each field-table row carries wire name, schema fragment, and byte encoder together, so a field can't be emitted without appearing in the generated schema. The old hand-rolled per-field assembly is gone.Wirenow owns the expanded wire tags (wireState/wireKind/wireAccess); the duplicates inGraphJsonwere removed.- Output byte-identical;
packed/--lazyuntouched (still inGraphJson).
Closes the silent-schema-drift gap: open additionalProperties let a new
producer field slip past validation undocumented.
AgdaDeps.Backend.Wiredescribes the v2 expanded wire shape once (field tables + a smallSchemaDocADT).agda-deps --emit-schemaprints the JSON Schema generated from it.schema/check_schema.py+ a CI step diff the generated schema against the committed oracle structurally; the committed file is never overwritten, so a wire-shape change must update it deliberately or the check fails.- Schema-only: no new runtime fields, no reroute; output byte-identical.
--version/-Vnow print the full build fingerprint (version + git rev + build date + GHC), the same string stamped intograph.jsonas"producer"; previously only the bare semver.--numeric-versionstays bare for tooling.- Documented
cabal install exe:agda-deps --overwrite-policy=alwaysso callers get a stable on-PATHbinary. AGDA_DEPS_GIT_REVoverride forBuildInfoTH:cabal installbuilds from an sdist with no.git, so the git splice otherwise reportsgit unknown; the env var lets an installer stamp the commit. In-tree builds are unaffected (git wins when present).
- Build fingerprint baked into every binary (
BuildInfo) — package version, git revision (+for a dirty tree), compile date, compiling GHC (TH for git, CPP for the date). Reported by--version. - Graph provenance in
graph.json— both emitters write"producer"and"nodeKeyVersion". Additive/optional; older JSON parses withnodeKeyVersiondefaulting to1.
Node identity now keys on nodeKey: prettyShow for top-level names
plus a @<binding-line> suffix for ._.-marked helpers, which
prettyShow otherwise renders identically across every same-named helper
in a module (so the last won and the rest's edges vanished). The
binding-site line is the one per-helper coordinate stable across
signature sources (NameId is not — hence the long-standing hashQName = prettyShow invariant). hashQName, the wire name, and edge
endpoints all derive from nodeKey; the expanded-edge filter resolves
by canonical string, not QName Ord. Top-level keys byte-identical.
Regression in test/Collision.agda.
--normalise-signatures reduces each type to semantic form before
reifying; --signature-implicits shows implicit/irrelevant args
(named to avoid clashing with Agda's --show-implicit). Both default
off, so default --with-signatures output is byte-identical.
Renders each definition's type — prettyTCM of defType, not
normalised, default printing, one line — as the optional per-def "type"
field in expanded JSON. Additive: absent without the flag, so default
output is byte-identical. Mirrored in YAML as with-signatures.
New flag --agda-html-dir=DIR links the HTML views to the pages
agda --html already wrote, instead of embedding snippets with
--with-source. Emitted into every view's data-loading prelude as the
AGDA_HTML_BASE JS var (trailing-slashed, or null when absent),
interpreted relative to the generated HTML file. Plumbed like every other
flag (Options + parser + commandLineFlags + YAML agda-html-dir +
NFData).
First consumer: the sunburst-hierarchy view. When AGDA_HTML_BASE
is set, module arcs and the centre disc gain an Open source ↗
affordance (module link needs no char-offset anchor — the filename is
<Module.Name>.html); external/builtin modules are suppressed.
Everything gated on AGDA_HTML_BASE, so output is unchanged when the
flag is absent.
--lenient-imports is an argv rewrite to --allow-unsolved-metas; any
--safe module in the dep closure (including stdlib) rejects it with
[SafeFlagPragma]. Documented in --help and README, recommending
--keep-going alone for safe-stdlib projects.
Opt-in resolution of the project's .agda-lib depend: closure into an
explicit --no-libraries -i <dir> … argv expansion. Use case: when
multiple versions of the same library are registered, Agda's resolver
picks ambiguously ([AmbiguousTopLevelModuleName]). New module
AgdaDeps.LibResolve reads ~/.agda/libraries, walks the transitive
closure, and pins the include dirs. Wired through Main.hs; YAML
resolve-deps: true. Falls back silently (with a breadcrumb) if there's
no .agda-lib or a dependency can't be resolved.
Previously, when a downstream module fatally failed, all def-level data from successfully-loaded modules was discarded — the graph collapsed to module-level only. Two fixes:
catchErrornow also wrapssetup, not justcheck mainFile, so aTCErrbefore checking starts (e.g.OptionError,SafeFlagPragma, library-resolution ambiguity) is reported, not escaped.- The failure branch re-drives
preModule → compileDef → postModuleper decoded module, accumulating results forpostCompile; each module's hooks are wrapped so one broken module is skipped.
Load-bearing: before the loop, every decoded interface's iSignature is
merged into stImports and stSignature cleared — without this,
downstream getConstInfo in contractIgnoredEdges fails with Unbound name panics on cross-module dep edges. Normal mode is byte-identical.
Drop the deprecated per-view shortcut flags (--cytoscape, --sigma,
--module-dag-pods, --ide-three-pane, --source-centric,
--notion-doc, --wiki-backlinks, --big-module-dag-pods,
--critical-path-holes, --progress-dashboard, --cartographic-atlas,
--sunburst-hierarchy, --reading-order-narrative,
--pixel-grid-overview). Deprecated 2026-05-27; passing one now errors
as an unrecognised option. The replacement remains --view=NAME (or
view: NAME in YAML). Removal drops setViewShortcutOpt and the
optSawViewShortcut field; the View ADT, viewSlug, and viewOpt
parser stay.
Cross-file CSE / lemma-extraction candidate detection over canonicalised
internal Terms.
AgdaDeps.TermCanon— canonical-form byte encoder forAgda.Syntax.Internal.Term. Two terms hash equal iff alpha-equivalent up to: de-BruijnVarindices, positions stripped,MetaVwildcarded, hidden bit preserved, provenance (ConInfo/ProjOrigin/…) dropped;QName/Sort/Level/LiteralviaprettyShow(same convention ashashQName, so module aliases collapse). Single bottom-up walk returns(encoding, depth, [(hash, depth)]), reusing the encoding at the parent.--with-term-hashes(off by default) — walksdefType+ every clause body; populates parallel_subtermHashes :: Maybe [Word64]and_subtermDepths :: Maybe [Int].--min-term-depth=N— emission threshold, default3(1disables filtering); cuts hash volume substantially.- Two optional wire fields in expanded JSON at
schemaVersion: 2:definitionSubtermHashesanddefinitionSubtermDepths, both parallel todefinitions; inner-array lengths must match (enforced at decode). Default-mode JSON is byte-identical (both fields absent, not empty).
agda-deps now reads a YAML config from a project-local file (or
explicit --config=PATH), promoting the shell-wrapper preambles projects
used into a real format. New module AgdaDeps.Config composes on top of
the existing surface via applyConfig :: A.Object -> Options -> Either String Options; Options and commandLineFlags are untouched.
- Top-level keys are kebab-case mirrors of CLI flag names; repeatable
flags accept YAML lists. Bad type / unknown key fails fast naming file
- key, exit 1.
- Discovery (first match wins):
--config=PATH>$AGDA_DEPS_CONFIG>./.agda-deps.yml(.yaml) > walk up to the first ancestor with a*.agda-liband pick its dotfile. - Merge order: defaults → config → CLI. A stderr breadcrumb fires
once when a config applies, unless
--quiet.
Example:
format: html
view: module-dag-pods
theme: dark
no-externals: true
keep-going: true
with-source: true
lazy: true
exclude:
- Agda.Builtin
- Data
no-source-for:
- Foreign
color-defined: "#4caf50"
color-postulate: "#f44336"
max-snippet-bytes: 1000000
json-mode: expanded
gzip: false
quiet: false
out-dir: build/depsTwo CLI simplifications shipped alongside:
--theme=default|light|dark|colorblind— single-flag preset for the four state colours. Explicit--color-*=#RRGGBBstill wins. YAMLtheme:.- Auto-format inference from
-o— when--formatis not set and-oends in.html/.json/.dot, the format is inferred. Explicit--format=…always wins; directories/extension-less names keep the default (dot). - Per-view shortcut flags deprecated — still work, but emit a
one-time stderr note; use
--view=NAME.
Added build-dep yaml >= 0.11 && < 0.12.
Expanded JSON now carries an optional definitionEdgesProvenance array,
parallel to definitionEdges, tagging each edge signature | body | where | with | unknown. AgdaDeps.Deps:
- Walks
defTypeandtheDefseparately. - Names in
defTypeareSignature; names intheDefareBody, refined toWithwhen the parent'sfunWithpoints at the target andWherewhen the qname'sprettyShowcontains the._.marker. - Combines via precedence
Signature > With > Where > Body > Unknown.
ADDef gained a strict _depsProv :: !(Map QName EdgeProv) with the
invariant M.keysSet _depsProv == _deps. IgnoredEdgeMap was widened to
carry provenance through contractIgnoredEdges — contracted edges
inherit the source side's tag. Packed mode emits an Int8 array
parallel to outTargets (0=signature,1=body,2=where,3=with,4=unknown).
Schema stays at v=2; the field is additive.
--lazy declared moduleFiles[m] entries for modules whose detail JSON
was never written (externals like Agda.Primitive, and project modules
where every def was filtered), so the JS fetched a 404. Root cause:
buildGraphJson populated moduleFiles for every module, but
buildModuleDetails only emitted files for modules with entries in
defsByModule.
Fix: buildModuleDetails now emits a stub detail JSON for every module
in moduleFiles absent from defsByModule, with discriminators
placeholder: true, reason: external|filtered|failed, and optional
externalPostulates. Eight view templates gained
placeholderReasonText + renderPlaceholderHTML and a guard at the
fetch site. Schema stays at v=2; non-lazy and expanded modes unaffected.
--no-externals was still emitting hundreds of stdlib module names. Root
cause: classifyExternalModules derived its external set only from
QNames with a resolvable source path, but Agda's compiler-builtins carry
rangeFile = Nothing, and stdlib modules visible only as import-edge
endpoints had no surviving QName.
Fix: classifyExternalModules now takes three signals — QNames,
precomputedModuleFiles, and all import-edge endpoint module names —
and treats a module as external when no signal places its source under
the project root. The keep predicate is applied to moduleFileMap too
(the single source of truth for module-level wire filtering) so
moduleFiles can't leak an external path. Default output unchanged.
A new top-level externals_summary tags dropped externals so a
diagnostic record of the trusted base survives --no-externals.
Collected before dropExternalDefs runs; omitted entirely when
--no-externals is off (byte-identical otherwise).
"externals_summary": {
"modules": ["Agda.Builtin.Bool", "Agda.Primitive", ...],
"postulates_by_module": {
"Agda.Builtin.Bool": ["true", "false"],
...
}
}buildExternalsSummary filters defs by state == Postulate, groups
unqualified names by module, and feeds both packed and expanded output.
The --skip-agda path initialises it to Nothing (no postulates seen).
lineper definition —_line :: !(Maybe Int)vianameBindingSite→rStart→posLine. Emitted in expanded JSON (omitted when unknown); packed unchanged.accessper definition —_access :: !(Maybe DefAccess). Agda discards the per-declAccesstag after scope-checking, so this uses a source-level pre-scan (findPrivateRanges) for top-levelprivateblocks at column 0;backfillAccessmatches each def's_lineagainst those ranges. Emitted as"access": "private" | "public".- Instance-declaration reverse edges — new
methodProvidersRefside-channel.recordInstanceMethods(incompileDefAD) recordsdefInstancebinders + projection-method QNames off head clause patterns;addInstanceMethodEdges(aftercontractIgnoredEdges) appends providers to any kept def's dep that's a method key. Additive.
ignoreDeffilters everydefCopy— module-instantiation copies forRecord/Datatype/Constructor/Projection, not justFunction; previously surfaced as ghost entries under the importer.hashQNameviaprettyShow— was hashing derived-Show, which includedNameIdmetadata that differs betweeniSignatureandstSignaturesources, collapsing duplicate nodes.- Dead-end private definition recovery in
postModuleAD— Agda'seliminateDeadCodeprunes unreachable top-levelprivatedefs fromiSignature; we diffgetSignature(pre-prune) against visited QNames and feed missing defs throughcompileDefAD.
Headline correctness fix: with-clauses elaborate to parent → with-NNN → target, and ignoreDef was dropping the with-NNN and
losing the edge. ignoredEdgesRef records the raw out-edges of every
ignored def; contractIgnoredEdges (in postCompileAD) rewrites each
kept def's _deps by expanding hidden refs into their transitive
non-hidden targets, via a topsort-based DP (Kahn) linear in hidden-node
count. Side-effect: ignoreDependency filtering moved from
computeDefAD to contractIgnoredEdges so raw hidden refs survive to be
expanded. Roughly doubled edge counts on the reference corpus.
Each ADDef carries _kind :: !DefKind derived structurally from
theDef: function/projection/datatype/record/constructor/
postulate/primitive/other. Emitted in expanded JSON so consumers
filter without string-scraping qnames. Agda 2.9's funProjection is
Either ProjectionLikenessMissing Projection; projection matches
Right{projProper = Just _}.
Captures open import M public and parameterised-module re-exports. The
producer walks each visited Interface's iScope over both
ImportedNS (plain open … public) and PublicNS (re-exports through
parameterised applications) namespaces. iScope is reconstructed from
iInsideScope at deserialise time, so the data survives cached .agdai.
Shipped 9 of 14 items from an external feature-request batch; the rest are in Backlog.md.
--version/-V/--numeric-version— early intercept inMain; reports the backend's version, not Agda's.--quiet— silences progress chatter viaAgdaDeps.Logging.info, backed by a globalquietRef.--no-externals— drops external modules from the rendered graph.--json-mode=packed|expanded— selects--format=jsonshape. Expanded shipsdefinitionsas[{id, name, module, state, x?, y?}]+ string edge pairs + explicitschemaVersionandmode.--lenient-imports— rewritten inMain.hsto--allow-unsolved-metas; useful with--keep-goingon projects with deliberate?holes.-odirectory auto-created viacreateDirectoryIfMissing True.OptionsgrewJsonMode, plusoptQuiet,optNoExternals,optJsonMode,optLenientImports.CLAUDE.mdgained the "State semantics" (D/P/H/F) and "v2 graph.json schema" sections.
Short-circuits the entire Agda pipeline. Main.hs routes --skip-agda
to AgdaDeps.SkipAgda.runSkipAgda, consuming the line-parsed module …
/ import … declarations AgdaDeps.Precompute already produces.
- Backend options parsed via
getOpt' Permute+runOptM. Precompute.parseHeaderfalls back totakeBaseName pathformodule _ where.GraphInputgotgiExtraModules :: Set Stringso orphan modules appear;renderHtmlFromInputexposed forSkipAgda.
Trade-off: no def graph, no D/P/H, no snippets. Module-DAG views render; def-level views show empty pods. Runs in milliseconds regardless of size.
Audited the pipeline for O(n²) hot spots; switched to strict
Map/IntMap/IntSet/Set folds throughout. Byte-identical output.
Layout.moduleGrouped—IntMapcons-accumulation (wasM.fromListWithwith(++)).GraphJson.bfsFrom/bfsDepths—Data.Sequenceinstead of list queues.- Six
nubsites collapsed to Set-based dedup. moduleEdgePairs— folds intoSet (Int, Int).Deps.collectAllQNames— folds intoIntMap QName.Csr.buildCsr— fusedlength+concatMapinto one strict sweep.Backend.computeQNamePositions—IntSet/IntMap, cheap membership test first.moduleStateCounts— strictdata Counts !Int !Int !Int !Int.buildSearchIndexbigrams gated above 50k names; JS falls back to linear scan.
Replaced the single cytoscape template with a View ADT and one template
per view under src/AgdaDeps/templates/views/, all consuming the same v2
graph.json:
module-dag-pods(default) — top-down DAG of expandable module pods via dagre.cytoscape— original compound-node viewer.ide-three-pane,source-centric,notion-doc,wiki-backlinks— definition-level views.sigma— WebGL via sigma.js + graphology + dagre; falls back to concentric above 3000 modules.big-module-dag-pods— viewport-virtualised port for ~100k modules. Dagre layout pre-computed Haskell-side (buildModuleDagLayout, Kahn + column-pack, O(V+E)), packed asmodulePodLayout; JS uses a spatial-grid + minimap.progress-dashboard,critical-path-holes— KPI / kanban dashboards.sunburst-hierarchy,cartographic-atlas,reading-order-narrative,pixel-grid-overview— hierarchical / textbook / heatmap views.
Agda's own --help lists hundreds of flags. Main.hs intercepts plain
--help / -h / -? and routes to printHelp, which uses
usageInfo over only the backend's commandLineFlags. Topic forms
(--help=warning) still forward to Agda; new --agda-help is rewritten
to plain --help for Agda's upstream printer.
Agda.Main.runAgda aborts on the first TCErr, so postCompile never
fires. Forked into AgdaDeps.ModuleExplorer:
partialBackendInteractioncatchesTCErrfromcheck mainFile, records the failing module viareportFailed :: String -> IO (), and drives each backend manually over the modules Agda did load.partialCompilerMainre-seedsstVisitedModulesfromstDecodedModules, runspreCompile, thenpostCompile.AgdaDeps.Driveris a thin shim wiringfailedModulesRefto the callback.
Plain agda only looks for .agda-lib in cwd, so invocations from
outside the project failed (library deps never consulted). Pre-process
argv before runAgdaArgs: canonicalize path-bearing entries to absolute
paths first (so a relative -o foo/ resolves against the user's cwd),
then walk up from each -i and each .agda/.lagda* positional for an
ancestor containing a *.agda-lib and setCurrentDirectory to it. Skip
the dance when --no-libraries, --library/-l, or --library-file is
already passed.
cabal.projectpins Agda as asource-repository-packagefromgithub.com/agda/agda(2.9.0 isn't on Hackage yet).agda-deps.cabal:Agda >= 2.9 && < 3.graphviz >= 2999.20to dodge older versions'<>ambiguity on GHC ≥ 9.- Adapted
_funWith :: Maybe QName→IsWithFunction QNameviaisWithFun.
--with-source embeds each definition's source in a slide-in drawer,
rendered with Agda's native semantic highlighting. Drives Agda's
defaultPageGen programmatically from postCompileAD:
- Per-module Agda HTML to a temp dir under
-o. - Extract the
<pre class="Agda">block from each file. - Per leaf QName: find binding line via
srcLocOf, compute paragraph bounds against the cached source, slice the highlighted HTML by line. - Inline
source+sourceLineon each node's JSON; remove temp dir.
Line slicing is safe because Agda's rendered <pre> keeps newlines as
plain text outside any <a> tag.
Definitions classified Defined / Postulate / Hole, tagged on
ADDef. Palette via --color-defined / --color-postulate /
--color-hole (defaults #4caf50 / #f44336 / #9c27b0); both DOT and
HTML honour it. Hole detection has three signals because Agda's
openMetasToPostulates rewrites ? into synthetic unsolved#meta.*
postulates before backends fire: a syntactic MetaV walk, the def's own
name, and references to synthetic-meta names.
Added --format=dot|html and a self-contained browser-explorable HTML
output backed by cytoscape.js. Modules as
collapsible compound parent nodes (cytoscape-expand-collapse); cytoscape
loaded from CDN, graph inlined as JSON. Output routed via -o/--out-dir.
Stripped flake.nix / flake.lock. Project builds purely via cabal.