julie-extract v2.10.0
v2.10.0
This minor release adds variable_ref identifier emission on top of v2.9.0.
It is an additive data change — new rows of an already-defined identifier kind
land in the existing identifiers table. It does not change the extraction
contract, the IdentifierKind enum, the identifiers schema, or any schema
version. SQLite schema stays 4, extract_contract_version stays 3, JSONL
schema stays 3.
New Capability: variable_ref Identifier Emission
Every general-purpose language extractor now emits a variable_ref identifier for
a bare name read — a symbol referenced by a plain value/receiver read that no
other identifier arm captured. Before this release, a symbol used only as a bare
read (return VisibilityUnknown;) or as the object/receiver of a static access
(GraphTraversal in GraphTraversal.Reach()) produced no identifier row, so a
downstream name-liveness consumer falsely flagged live symbols as dead. variable_ref
closes that gap by name.
The emission contract (6 rules, locked by the C# reference implementation)
Emit IdentifierKind::VariableRef (serialized variable_ref) for a name node N
when ALL hold:
- Read in value or receiver position — N is a reference used as an expression,
operand, argument, initializer, return value, collection element, OR the
object/receiver of a member access (XinX.Y/X.Y()/X::Y), OR a
member-reference LHS in an initializer/named-argument context (object-initializer
members, attribute named arguments, and equivalent per-language constructs). - Not already emitted by another arm — N is not a call callee (
Call), the
accessed.nameof a member access, or a type usage (TypeUsage).variable_ref
is the complement of the arms already in that extractor. - Not a declaration name — not the defining identifier of a
type/method/property/field/enum-member/parameter/local, a label, or an
import/using-alias LHS. - Not a write-only target — not the LHS of a plain assignment (
x = 5). A
compound assignment (x += 1,x ||= y) IS a read and emits.out/ref-style
argument slots may emit as reads (the safe direction — bare write-slot targets are
locals, which are never dead-code candidates). - Not a keyword/builtin — reuses each language's existing builtin/keyword filter;
never emitstrue,null,this,base, contextual keywords, or builtin type
names. containing_symbol_idis set via the existing byte-range containment helper,
exactly as the sibling arms do.
Non-resolvable by design. variable_ref is intentionally not wired into the
resolver tier chain (ReferenceKind::from_identifier_kind stays call / type_usage
/ member_access). The new rows are consumed downstream by name-match only, never
by the tier-1..4 resolution overlay. A resolvable ValueRef kind is a possible future
enhancement, explicitly out of scope here.
Per-Language Coverage
Measured on the real-repo dogfood extract (the julie-extractors workspace itself, one
full pass) — SELECT language, COUNT(*) FROM identifiers WHERE kind='variable_ref' GROUP BY language. Every general-purpose language emits variable_ref; no
general-purpose language is at 0.
| Language | variable_ref rows |
Notes |
|---|---|---|
| rust | 169,741 | dominates the corpus (large Rust workspace) |
| qml | 431 | pre-existing arm, unchanged |
| javascript | 340 | |
| r | 84 | pre-existing arm, unchanged |
| python | 74 | |
| php | 58 | |
| go | 53 | |
| csharp | 49 | reference implementation |
| jsx | 37 | |
| typescript | 34 | |
| tsx | 34 | |
| elixir | 29 | |
| java | 31 | |
| zig | 25 | |
| lua | 22 | |
| scala | 22 | |
| kotlin | 20 | |
| ruby | 16 | |
| gdscript | 15 | |
| razor | 16 | |
| vue | 14 | |
| cpp | 13 | |
| dart | 12 | |
| vbnet | 12 | |
| powershell | 11 | |
| swift | 11 | |
| c | 10 | |
| bash | 8 | $var / ${var} expansion reads (added) |
| css | 3 | var(--x) custom-property reads (added) |
| yaml | 3 | alias references (*name), pre-existing arm |
not_applicable (no variable_ref construct in the code-symbol sense; capabilities.json
records the determination): html, json, markdown, regex, sql, toml. SQL
variable/column reads already surface name-visibly as member_access (covered via
member_access — not re-emitted as variable_ref); json/toml/markdown emit no
identifiers at all.
Open-Gaps Ledger
Genuine grammar/scope limitations recorded during the rollout (honest under-report,
never guessed rows). These are precision-first misses; the feature's contract is
high-precision candidates, so under-reporting is the correct failure direction.
- kotlin — tree-sitter-kotlin-ng lexes a simple
$nameinterpolation as
string_content, so those reads are structurally unrepresentable and do not emit.
${name}interpolation does emit. - javascript (plain JS) — plain-JS JSX component names have no JSX arm in the JS
extractor (the TSXCallarm owns them); component-name reads emit fortsx/jsx,
not plain.js. - yaml — the alias
variable_refbinds a target at extraction (inline anchor
resolution, a pre-existing behavior). This predates the non-resolvable contract; it
is correct for file-local YAML anchors and was left unchanged rather than normalized. - rust — macro callees have no identifier row (Call-arm scope); bare-name constants
in match patterns are excluded (would need scope analysis; no guessed rows). - zig — bare return types are not covered by the zig
TypeUsagearm
(kind-honesty exclusion).
Pre-existing defects surfaced during the rollout (follow-up issues, not fixed here)
These were observed while adding the arms and are documented for follow-up. None were
introduced by this release.
- powershell — the vendored tree-sitter-powershell grammar emits
invokation_expression(typo), so the PSCallarm never fires ($obj.Method()
emits noCallidentifier today). - gdscript — a vestigial subscript
MemberAccessarm reads anindexfield the
6.1.0 grammar no longer produces. - elixir — the dot arm and the standalone alias arm double-emit a module receiver
astype_usage(two identical-span rows perModule.fun()); present in the base
goldens, not avariable_refregression. - sql / bash — SQL
DECLARE/SETvariables and bashfor-loop bindings are not
extracted as symbols, so there is no declared symbol for liveness to test against
(symbol-side gap, not an identifier-side gap).
Bundled Fix: v2.9.0 Scan-Performance Regression (437f91c)
v2.9.0's in-transaction resolution pass wrapped ~125k per-row overlay upsert/update
statements in a single SQLite SAVEPOINT. Each statement then walked a
memjrnlTruncate over the growing savepoint journal (99.9% of profiler samples),
turning the resolution write into a quadratic. On this repo the full scan regressed
from ~6.5 s (v2.8.0) to 425.9 s. The fix collects the overlay writes and
flushes them in batches, preserving the non-fatal rollback contract and adding a
perf regression test through the resolution-hook seam (the harness blind spot that
let the regression ship). Post-fix full scan: ~10.2 s in the fix-branch e2e check.
Honest scan-duration line item (restored — the v2.9.0 release evidence omitted the
overall scan wall-clock):
| Version | Full scan wall-clock (this-class repo / Miller repo) |
|---|---|
| v2.8.0 baseline | ≈ 6.5 s |
| v2.9.0 (regressed) | 425.9 s |
v2.9.0 (post-fix 437f91c) |
≈ 10.2 s (fix-branch e2e) |
| v2.10.0 (measured, Miller repo, 3 runs) | 7.617 s median (min 7.525 s, max 7.851 s) |
The v2.10.0 measurement is on the Miller repo (838 files) with the release binary; the
seconds-scale result confirms the savepoint quadratic is gone.
Performance & Growth Deltas (Miller repo, release binary, 3 runs)
The performance harness generates baselines/summaries with no enforced identifier-count
budget, so the growth deltas are recorded and judged explicitly here against the
escalation triggers (stop-and-report if identifier rows grow >5× or scan wall-clock
grows >2×).
| Metric | v2.9.0 baseline | v2.10.0 | Delta | Trigger |
|---|---|---|---|---|
| identifiers (total) | 94,721 | 187,003 | 1.97× | < 5× ✓ |
— call |
52,498 | 52,498 | 1.00× | unchanged |
— member_access |
26,271 | 26,271 | 1.00× | unchanged |
— type_usage |
15,952 | 15,952 | 1.00× | unchanged |
— variable_ref (new) |
0 | 92,282 | — | additive |
| scan wall-clock (median) | ≈ 6.5 s (v2.8.0) | 7.617 s | 1.17× | < 2× ✓ |
| artifact size | 237 MB | 348 MB | 1.47× | report-only |
The pre-existing kind counts are byte-identical to the v2.9.0 baseline — the only
growth is the additive variable_ref rows, which confirms the arm is a true complement
(no double-emit, no spurious rewrite of existing rows) across the whole Miller corpus.
Known recall cost (accepted, precision-first). Emitting all bare reads means any
candidate symbol that shares an exact name with any read binding anywhere in the
workspace is masked alive by name-match. Case-sensitive/BINARY matching limits
collisions in camelCase languages (C#); snake_case languages (rust, python, ruby) mask
more. Under-reporting is the correct failure direction for a high-precision candidate
feature.
Capability Honesty
fixtures/extraction/capabilities.jsonrecordsvariable_refin the
kind_coverage.identifiers.supportedlist for every emitting language, and in
kind_coverage.identifiers.not_applicableforhtml,json,markdown,regex,
sql, andtoml.- The strict data-quality gate
(node scripts/language-data-quality-report.mjs --strict) stays at
silent_cells: 0,quality_bar_debts: 0. - Every emitting language is fixture-backed: the language's golden
fixtures/extraction/<lang>/**/expected.jsoncarries the realvariable_refrows,
regenerated and hand-verified per batch.
Contract Changes
- Rust package versions change from
2.9.0to2.10.0across all three crates
(julie-extract-artifact,julie-extract-cli,julie-extractors). - No schema change. SQLite schema stays
4,extract_contract_versionstays3,
JSONL schema stays3.variable_refis an already-definedIdentifierKindand a
valididentifiers.kindvalue; this release only emits more rows of it. This is why
the bump is minor, not major. - The resolver contract is unchanged:
variable_refrows are non-resolvable and never
enter the tier chain;identifiers.target_symbol_idstaysNULLfor them. - Parser dependency versions are unchanged.
Pre-Publish Adversarial Review Fixes
An adversarial source review before publish surfaced two predicate defects,
both lead-verified with live probes and fixed in this release:
-
java: fully-qualified static-call receivers (
com.acme.GraphTraversal.reach())
now emit the terminal class name (member_access), matching FQ static-field
behavior. Previously such classes were invisible to name-liveness. (4ddcb63) -
python: match-statement pattern bindings (
case [*items]:,
case _ as handler:,case Point(x=px):) no longer leak asvariable_ref
reads; dotted value patterns (case Color.RED:) remain non-emitting
(safe-miss direction, see Open-Gaps Ledger). (61bb384) -
razor (fix round 2): component-attribute and
@bind-Valuevalues now
parse (vendored grammar fixed in the anortham/tree-sitter-razor fork, rev
cf7b0e5; upstream PR tris203/tree-sitter-razor#27); identifiers inside them
emit reads. (f28da33) -
csharp (fix round 2):
A * Bargument-position multiplication mis-parsed
as a pointer declaration now recovers both operands as reads outsideunsafe
contexts. (c08f1e6)
Verification
Local prep gates (all green at the release-prep commit):
cargo build --release -p julie-extract-cli: 0 warnings; binary reports
julie-extract 2.10.0cargo test -p julie-extractors --lib: 2,817 passed, 0 failedcargo test -p julie-extractors --features test-golden --lib golden_fixtures_match_canonical_extraction: 1 passed (golden clean)cargo clippy -p julie-extractors --lib: 0 warningsnode scripts/language-data-quality-report.mjs --strict: 36 languages,
silent_cells: 0,quality_bar_debts: 0cargo xtask dogfood repo --root . --out-dir target/dogfood/julie-extractors:
scan ok; per-languagevariable_refcoverage table above; no unexplained zeroscargo xtask performance baseline --root /Users/murphy/source/miller --out-dir target/performance/variable-ref-miller --binary target/release/julie-extract --runs 3: scan 7.617 s median; growth deltas above (1.97× identifiers, 1.17× scan)cargo xtask release preflight --version 2.10.0: ok
Publish & Assets
Published 2026-07-07T22:58:08Z as
julie-extract v2.10.0
from commit 5fd812f800d8d84cdf4c9ac5e1ce978ab27c2c75. The Release Binaries
workflow run
28904104476
completed successfully and uploaded four assets:
| Target | Asset SHA-256 |
|---|---|
aarch64-apple-darwin |
dcdd59b24ad5724bbc2edf5c9bef1a87500cfc7ed2f8aaf25d2f61ce019b6fe7 |
x86_64-apple-darwin |
8cabfa2ab4d617bb301a790494d8b3c5382450faaceadc55b4cfd44970fb3119 |
x86_64-pc-windows-msvc |
2380935f7fa2b940bd0927c422543f30b0ce4c52d1979d2941faafd7a48603df |
x86_64-unknown-linux-gnu |
32e9d57d45c72e4807dbb88e1c5832105d6cd4faf62eaeada40997e762ef4e01 |
Downloaded archive digests matched GitHub metadata, packaged binary checksum files
verified, and the downloaded Apple Silicon binary reported julie-extract 2.10.0.