Skip to content

julie-extract v2.10.0

Choose a tag to compare

@github-actions github-actions released this 07 Jul 22:58

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:

  1. 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 (X in X.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).
  2. Not already emitted by another arm — N is not a call callee (Call), the
    accessed .name of a member access, or a type usage (TypeUsage). variable_ref
    is the complement of the arms already in that extractor.
  3. 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.
  4. 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).
  5. Not a keyword/builtin — reuses each language's existing builtin/keyword filter;
    never emits true, null, this, base, contextual keywords, or builtin type
    names.
  6. containing_symbol_id is 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 $name interpolation 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 TSX Call arm owns them); component-name reads emit for tsx/jsx,
    not plain .js.
  • yaml — the alias variable_ref binds 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 TypeUsage arm
    (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 PS Call arm never fires ($obj.Method()
    emits no Call identifier today).
  • gdscript — a vestigial subscript MemberAccess arm reads an index field the
    6.1.0 grammar no longer produces.
  • elixir — the dot arm and the standalone alias arm double-emit a module receiver
    as type_usage (two identical-span rows per Module.fun()); present in the base
    goldens, not a variable_ref regression.
  • sql / bash — SQL DECLARE/SET variables and bash for-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.json records variable_ref in the
    kind_coverage.identifiers.supported list for every emitting language, and in
    kind_coverage.identifiers.not_applicable for html, json, markdown, regex,
    sql, and toml.
  • 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.json carries the real variable_ref rows,
    regenerated and hand-verified per batch.

Contract Changes

  • Rust package versions change from 2.9.0 to 2.10.0 across all three crates
    (julie-extract-artifact, julie-extract-cli, julie-extractors).
  • No schema change. SQLite schema stays 4, extract_contract_version stays 3,
    JSONL schema stays 3. variable_ref is an already-defined IdentifierKind and a
    valid identifiers.kind value; this release only emits more rows of it. This is why
    the bump is minor, not major.
  • The resolver contract is unchanged: variable_ref rows are non-resolvable and never
    enter the tier chain; identifiers.target_symbol_id stays NULL for 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 as variable_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-Value values 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 * B argument-position multiplication mis-parsed
    as a pointer declaration now recovers both operands as reads outside unsafe
    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.0
  • cargo test -p julie-extractors --lib: 2,817 passed, 0 failed
  • cargo test -p julie-extractors --features test-golden --lib golden_fixtures_match_canonical_extraction: 1 passed (golden clean)
  • cargo clippy -p julie-extractors --lib: 0 warnings
  • node scripts/language-data-quality-report.mjs --strict: 36 languages,
    silent_cells: 0, quality_bar_debts: 0
  • cargo xtask dogfood repo --root . --out-dir target/dogfood/julie-extractors:
    scan ok; per-language variable_ref coverage table above; no unexplained zeros
  • cargo 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.