Current section
Files
Jump to
Current section
Files
CHANGELOG.md
# Changelog
## 0.1.21 — 2026-09-16
- Depends on `roux` from Hex (`~> 0.1.4`); every dependency is a Hex
package now, so scry itself is published.
## 0.1.20 — 2026-09-16
- Depends on `panoptes` from Hex (`~> 0.11`) instead of a GitHub tag, so
an argus release no longer needs a scry release to follow it.
## 0.1.19 — 2026-09-16
- Argus 0.11.0 is the `panoptes` package and application (the modules
keep the `Argus` namespace); scry depends on `:panoptes`. `mix argus`
is gone from argus — `mix scry` is the way to run the analyses.
## 0.1.18 — 2026-09-16
- Fixes 0.1.17, which sorted a module's memoized rows by symbol id. An
id's value depends on the order the table met its symbol in — which
parallel extraction does not fix — so row order, and everything
downstream that keeps it (the supervision tree's resource lists),
varied from run to run for the same beam. Rows are sorted as strings
before they are interned.
## 0.1.17 — 2026-09-16
- **Memoized rows are interned.** Extraction hands back tuples of
`Argus.Symbols` ids (argus 0.10.0's `format: :interned`) minted against
one of the database's `Roux.Intern` tables, so the ids persist in the
manifest with the rows they describe. Every copy of the fact set —
the module memos, the program union, the per-relation slices, the
served-value cache, the manifest — is a third to a fifth of its former
size. On a 600-module project peak memory went from 4.2 GB to 2.2 GB
(warm: 2.5 GB to 1.5 GB), a cold `mix scry --all` from 38 s to 30 s
and a warm run from 5.0 s to 4.3 s; on a 64-module project the peak
went from 639 MB to 324 MB. Findings are unchanged. New queries:
`relation_rows` (interned) and `relation_digest` over the strings the
files hold; `relation_facts` keeps returning strings for consumers
outside this layer, and `Scry.Symbols.for_db/1` is the table to decode
memoized rows with (planchette's focus path does).
## 0.1.16 — 2026-09-16
Performance: a cold `mix scry --all` on a 600-module project went from
126 s to 38 s, its peak memory from 7.7 GB to 4.2 GB, and a warm run
from 15.7 s to 5.0 s (on a 64-module project: 12.5 s → 7.0 s cold,
1.6 s warm). Findings are unchanged.
- **One pass over the modules builds every relation's rows.** The
per-(module, relation) query it replaces read a module's whole fact
map out of the memo table once per relation — 47,000 reads on that
project, most of the run. `program_relation_facts` reads each module
once; `relation_facts` is a lookup into it and still backdates per
relation, so the projections and solves downstream validate without
executing exactly as before. `module_relation_facts` is gone.
- **Fact directories are named from per-relation digests**
(`relation_digest`), computed once per relation change rather than by
re-serializing every projected row per analysis, and each relation's
file is written once under `scry_souffle/relations/` and hard-linked
into every directory that projects it.
- **Extraction runs across the schedulers.** The graph executes one
query at a time, so every module was extracted serially; the runner
now extracts the modules that cannot be memo hits in parallel before
demanding, and `module_extraction` picks the result up (keyed by the
canonical beam's digest, so a beam that moved in between is extracted
again).
- **An unchanged run does not rewrite the manifest.** No input moved, so
no revision advanced and every entry is as the manifest already has
it; rewriting it was most of a warm run.
- Roux 0.1.3: memo hits are served from a per-process cache instead of a
fresh copy out of ETS on every read, and manifests serialize memo
entries one at a time (format 2 — the first run after upgrading is
cold).
## 0.1.15 — 2026-09-16
- Argus 0.9.1 (schema 31): `whereis_race` only for results used without
a nil check, and a literal `System.cmd` command no longer counts as
code execution whatever its arguments. Warm manifests re-extract once.
## 0.1.14 — 2026-09-12
- Argus 0.9.0: the memory release. No finding changes. Solves that
joined the call graph's transitive closure (up to 690 MB each on a
2,400-module project) now propagate from the few functions that
matter and run in under 160 MB; the batch extractor streams facts to
disk instead of holding the program's fact set in memory. Scry's own
per-module extraction and per-analysis solves pick both up unchanged.
## 0.1.13 — 2026-09-12
- Argus 0.8.1: no analysis changes; `mix argus` and the project script now
point a missing `souffle` at the install page before extracting.
## 0.1.12 — 2026-09-12
- Fixes 0.1.11, which listed the `Argus.Extractors.Ports` extractor argus
0.8.0 folded into `Argus.Extractors.ApiCalls`; every extraction raised
`UndefinedFunctionError`. The resource extractors the supervision-tree
overlay needs are now `ETS` and `ApiCalls`.
## 0.1.11 — 2026-09-12
- Argus 0.8.0 (schema 30): the consolidation release. No finding scry
reports changes except the ETS false positive it fixes on Erlang
application modules; extraction is 9-37% faster on the corpus.
## 0.1.10 — 2026-09-11
- Pins argus v0.7.3. 0.1.9 said it did and still pinned v0.7.2; a
project depending on both scry and a newer argus saw a divergence.
## 0.1.9 — 2026-09-11
- Elixir requirement lowered to `~> 1.18` (roux v0.1.1, argus 0.7.3);
OTP 28 remains required. The compiler's fixture projects declare the
same.
## 0.1.8 — 2026-09-11
- Argus 0.7.2: `init_waits_on_blocking_server` counts only unbounded
handler operations; `permanent_child_stops_normally` and inferred
`rest_for_one_orphaned_children` rows are `:info`.
## 0.1.7 — 2026-09-11
- Argus 0.7.1 (schema 28): `monitor_leak` reports a monitor whose ref is
discarded at the call site, and reaches helpers through closures.
## 0.1.6 — 2026-09-11
- Argus 0.7.0 (schema 27): ten new rules from replaying historical OTP
bug fixes — supervisor management calls from `init/1`, an init waiting
on a server whose handler blocks, permanent children that stop
themselves, `rest_for_one` owners of processes in earlier siblings,
`trap_exit` without an `{:EXIT, ...}` clause, `handle_info/2` without a
catch-all, monitors a server never releases, write-only ETS tables,
gen_statem states without an `:info` catch-all and timeouts nobody
handles. Trees built with `Keyword.get/3` defaults and cons-built child
lists extract in source order.
## 0.1.5 — 2026-09-11
- Argus 0.6.1: supervision trees defined outside `Supervisor` modules
(a GenServer's `init/1` calling `Supervisor.start_link/2`) and child
specs built by helpers and comprehensions are extracted, so more
`sync_call_in_init` findings are proven safe by an earlier sibling.
## 0.1.4 — 2026-09-11
- Argus 0.6.0: stage 0 now also yields `unconditional_call_edge`, which
the fact projections carry like `call_edge` and `call_site`;
`sync_call_in_init` findings say whether the blocking call is
conditional on a branch in `init/1`.
## 0.1.3 — 2026-09-11
- Argus 0.5.1: literal facts drop Logger location metadata (a comment
above a `Logger.warning` no longer re-solves analyses), coupling
findings are graded call vs cast, unverified `sync_call_in_init` rows
are `:info`, and any module with `handle_info/2` counts as consuming
its task replies.
- Beams are hashed and memoized in canonical form (`Scry.Beam.canonical/1`
drops the `ExCk` and `Docs` chunks). Elixir rewrites the `ExCk` chunk of
every compile-time dependent when a module is recompiled, so a
comment-only edit re-extracted two to six modules on the projects
surveyed (Finch, Postgrex, Oban, Cachex, ...) before the semantic
cutoff caught them; now it re-extracts exactly the edited one.
## 0.1.2 — 2026-09-11
- `mix scry` compiles with `--no-prune-code-paths`. In a project with an
explicit `applications:` list, Mix's code-path pruning removed scry
itself after the compile step and the task died with `Scry.Config is
not available` (found on `amqp`). The `:scry` compiler in such projects
needs `prune_code_paths: false`; the README says so.
## 0.1.1 — 2026-09-11
- `makeup_elixir` and `makeup_erlang` are optional dependencies. A hard
dependency collided with the `only: :docs` / `only: :dev` restriction
projects put on the makeup lexers (Phoenix declares them that way, and
ex_doc pulls them in `only: :dev` everywhere else), which made scry
impossible to add to such a project. Add the lexers to your own deps to
keep highlighted terminal frames; without them frames render plain.
## 0.1.0 — 2026-09-11
Initial release: an analysis-only Mix compiler for BEAM projects.
- **Scry is the compiler and the shared analysis layer, nothing more**:
the supervision tree, flowistry focus/slicing, and the debug twin
(`Scry.SupTree`, `Scry.Flow`, `Scry.DebugSlice`, and the
`supervision_tree`, `refined_line_table`, `debug_twin`,
`debug_bundle`, `module_flow`, `function_flow` queries) moved to
planchette, whose LSP is their only consumer and the only frontend
that supplies the `source_text` input they need. Query names are
unchanged, so planchette manifests stay warm. Scry no longer depends
on gloss or beam_spy.
- **Extraction memos follow the argus schema**: `module_extraction`
now reads `env_fingerprint` (which carries `Argus.Schema.version/0`),
so a warm manifest re-extracts after an argus upgrade instead of
serving rows the previous encoder wrote. The compile-time
`Argus.Schema.Pin` is gone with it — argus removed the mechanism.
- **Labels span the line's code**: finding anchors render as inline
labels under the anchored line's code extent (first non-blank column
to the end of the trimmed line) instead of column-1 bracket labels.
BEAM anchors are still line-granular; a source line that cannot be
read degrades to a one-column span.
- **Syntax-highlighted terminal diagnostics**: the stderr frames now
colorize their source excerpts (via pentiment's optional makeup
lexers, included as scry dependencies). The `details` field on
`Mix.Task.Compiler.Diagnostic` and the sidecar remain plain text.
- **The shared analysis layer**, extracted verbatim from planchette:
`Scry.Analysis` (per-module argus extraction → semantic-facts cutoff
seam → per-relation projections → content-addressed Souffle fact
dirs → solve → line-free findings → late line resolution). Query
names are the ABI — planchette's LSP consumes the same layer with its
in-memory compile frontend. `souffle_solve` re-materializes its fact
directory when a concurrent prune removed it (the scratch window is
shared between LSP sessions and compiler runs).
- **`mix compile.scry`** (`compilers: Mix.compilers() ++ [:scry]`): a
disk-beam roux frontend over the ebin the stock compilers just
wrote, with cross-run incrementality via `Roux.Lang.Manifest` — a
warm run re-analyzes nothing, a comment-only edit re-extracts one
module and re-runs zero solves, and prior findings re-emit from memo
hits on every run. Configuration under the `scry:` project key:
`analyses`, per-analysis `severity` overrides, `ignore`
(modules/files), `include_deps`, `fail_on`, and `souffle`
(`:warn` degrades with one notice and demands no solves — nothing
poisons the manifest, and the souffle version in the environment
fingerprint heals everything when the solver appears; `:require`
makes it an error).
- **Pentiment-rendered diagnostics**: labels on the line-granular
anchors annotated with the finding's `at_label`,
cross-file evidence as `├─` continuation frames against their own
files, detail as a note, and `help` remediation trailers. Editors
get a short message + line via the standard compiler diagnostics;
terminals get the full frame.
- **`mix scry`**: the credo-style one-shot over the same manifest —
positional analysis selection, `--all`, `--list`, `--format json`
(stable machine schema), `--fail-above N`, `--include-deps`,
`--force`.