Status: SURVEY COMPLETE (2026-07-20), port not started. Survey basis: every
TS-side branch a .scala/.sc file exercises, with file:line anchors as of
45a53eb (HEAD at survey time, clean main; a6c62d7..45a53eb touched only
grammars.ts/kernel/index.ts/wasm inside src/extraction — tree-sitter.ts,
languages/scala.ts, function-ref.ts, and tree-sitter-helpers.ts are
byte-unchanged, so kotlin/swift-checklist anchors into shared code remain
valid). Every grammar-shape claim below was probed against the
production vendored wasm (dist/extraction/wasm/tree-sitter-scala.wasm,
sha256 7945b13e…, identical to src/…), and every extraction-behavior claim
was pinned against the real dist/ extractor (extract-*.txt ground-truth
dumps) — not derived from code reading. childForFieldName truth tables were
probed directly (brace-field.out, the FIELDS sections of cst-snippets.txt)
because this grammar attaches field names to ANONYMOUS tokens — CST dump
labels alone mislead (the swift lesson, §Extension). Probe scripts + dumps in
the session scratchpad svy-scala/ (§Probe artifacts). Read WITH
docs/design/rust-kernel-migration-plan.md (§0a recipe, §2 boundary, §4
tracker row "scala", §5 gates) and the format precedents
(kotlin-kernel-port-checklist.md — the closest sibling: JVM family, hook
property branch, re-encode gate — and swift-kernel-port-checklist.md).
Blocking findings: none — but three eyes-open items. (1) The wasm is
already vendored and needs NO bump: VENDORED_WASM_LANGS has contained
scala since 2026-05-07 (#91), and the vendored wasm is table-identical to
tree-sitter/tree-sitter-scala master@0aca5d0a6f (verified twice: the
batch-4 positional table compare, and this survey's clone-sha match). The port
is vendored-grammar-C only (kotlin mechanism) — there is no behavior-delta
gate to run. (2) Error incidence is bimodal: mainstream Scala-2-style
repos are clean (os-lib 0.00%, cats 1.80%) but bleeding-edge Scala-3 code is
not (scala3 compiler/src 9.88%, library/src 17.79% — capture-checking ^
types), and ~40–60% of erroring files are PHANTOMS (hasError=true, zero
ERROR/missing nodes — trust the flag). Sweep guidance in §Architecture #6.
(3) scala has 32 real fields but three load-bearing places where
childForFieldName returns the FIRST of SEVERAL same-named fields — or an
anonymous { token (import path, curried parameters, extension body).
The walker must reproduce first-match-wins over the full (named+anonymous)
child list, not "the" field (§Extractor config, §Extension).
src/extraction/wasm/tree-sitter-scala.wasm, sha256
7945b13e6f9b15b578c5e5e4e60253c049fec07c531518163f3415a76c0621aa
(src == dist byte-identical), ABI 15, STATE_COUNT 26650, 357+5 symbols,
FIELD_COUNT 32. In VENDORED_WASM_LANGS (grammars.ts:292) since #91
(2026-05-07). Mapping scala: 'tree-sitter-scala.wasm' (grammars.ts:38).0aca5d0a6fe115b16d55cb100e1bb05e7fb11385 (2026-04-22 "chore:
generate and sync from ec71cd9d51" — the post-v0.26.0 fix batch:
scala2-compiler-100%, lambda-body restrict, wildcard self-type). The clone's
src/parser.c sha256 equals the batch-4 probe's positional-table-verified
copy (bc3c3c79…), and that probe found the wasm's kind/field tables
positionally identical to it. The v0.26.0 tag == crate 0.26.0 is 26620
states — 30 states BEHIND our wasm — so a crate pin would be a silent
downgrade; later master (fc99b1bd, Apr-27) is a 23959-state restructure —
a future-bump candidate, NOT this port. Full record:
../scratchpad/batch4-grammar-probe.md.0aca5d0a6f clone into codegraph-kernel/grammars/scala/ (shas recorded in
a comment; survey record grammar-shas.txt):
src/parser.c bc3c3c794f19461d99d04de6c31d57fa3e41243509b9ab023a9b88ed3273d102 (34,970,232 bytes — 35 MB, the biggest grammar in the tree; expect a slow cc step)src/scanner.c e4ba242568ee3493015598997bf60f613802616eade62717c21109287ef64752 (17,731 bytes — a REAL external scanner: significant-indentation + interpolation; it handles \r explicitly, scanner.c:476)src/tree_sitter/alloc.h b29c1c9f…, array.h 31e60a1b…, parser.h 180b893c…bindings/rust/build.rs flag set — cc::Build
.std("c11").include(grammars/scala).flag_if_supported("-Wno-unused"),
msvc -utf-8; compile parser.c + scanner.c; symbol tree_sitter_scala
(parser.c:1199034). Same shape as the kotlin block (build.rs:14-22).tree-sitter-language shim already present (kotlin). langs.rs:
extern "C" { fn tree_sitter_scala() -> *const (); } +
"scala" => LanguageFn::from_raw(...) + LANGUAGES 15 → 16.__tests__/kernel-grammar-parity.test.ts:39 GRAMMAR_LANGUAGES += 'scala'
— the id-by-id table compare against the vendored wasm proves the C build
is the same revision (ABI 15 / 26650 states / 32 fields must match).tree-sitter generate).crlf-cst.cjs: indent/
torture/docs/vref/misc/ext/script all sexpEqual=true, no error flips).
Extraction under CRLF is byte-identical except the multi-line-block-comment
docstring \r retention (§Docstrings — a shared js_multiline_strip
concern, not a scanner one). The kernel parses UTF-8 while wasm parses
UTF-16 — error-recovery differences are exactly what per-file
has_error() → defer: guards; nothing scala-specific beyond the elevated
incidence below..scala/.sc ≤1 MiB, error-sweep.cjs)| Repo | files | hasError | rate | of which PHANTOM |
|---|---|---|---|---|
| os-lib (small) | 59 | 0 | 0.00% | — |
| cats (medium) | 835 | 15 | 1.80% | 4 (27%) |
| scala3 compiler/src | 577 | 57 | 9.88% | — |
| scala3 library/src | 652 | 116 | 17.79% | 69 (59%) |
| scala3 whole repo | 18,411 | 1,991 | 10.81% | (incl. tests/ = deliberately-invalid neg fixtures, 11.78%) |
.sc files: 14 in scala3 (1 error); os-lib/cats have none. Error classes
(sampled err-samples.out, minimized errvariants.out / phantom-min.out):
^ — def f(x: List[Int]^): Int = 1 (the whole
scala3 library uses import language.experimental.captureChecking).
cats' scala-2 macro files (FreeFoldStep.scala) phantom too. The kernel
must defer on the FLAG, never on ERROR-node presence (kotlin lesson,
worse here).end used as an identifier — end is hard-reserved by the grammar
(end match, val end = 1 both ERROR). cats AndThen.scala.extends Eq[A]()(ev) ERRORS (plain Base(1)(2) parses clean —
extract-super2.txt). cats kernel instances.type ⊥ = Nothing (cats
package.scala).given … with { } syntax — given x: C with { … }
ERRORS (the = new C {} and colon forms parse fine)..type unions,
compact catch case, braceless match at margin) — scala3 compiler files.All classes are grammar-inherent and identical across arms by construction (same grammar revision compiled twice). Deferral guidance: §Architecture #6.
scalaExtractor has no preParse
(languages/scala.ts — whole file) → the kernel/index.ts preParse hoist is a
no-op; no POST_PASSES entry → tryKernelExtractRaw stays eligible.playResolver (resolution/frameworks/play.ts:30, languages: ['scala',
'java', 'yaml'], and it HAS extract()), via parse-worker.ts's
frameworksNeedDecode check (parse-worker.ts:95-100). detect() (play.ts:32):
build.sbt matching /playframework|"play"|sbt-plugin|PlayScala|PlayJava/i,
OR conf/routes exists, OR conf/application.conf exists. None of the
three gate repos trips it (no conf/, no playframework in build.sbt) —
they exercise the raw buffers-to-store transport; a Play app (or any repo
with a root conf/application.conf — akka-style apps can!) is the
decoded-path smoke check. The Play extract() itself only produces output
for conf/routes/*.routes files (isPlayRoutesFile, grammars.ts:222-228)
which are NOT scala files (extensionless → no-grammar path) — the cost of
detection is only the decode, not wrong output.codegraph-kernel/src/scala.rs), registered in
langs.rs; per-file has_error() → defer:. kotlin.rs is the closest
crib (visitNode-hook property branch, classify-by-node-type, re-encode
gate, JVM import shapes) but scala diverges in TEN places, each detailed
below: (a) no namespace node ever (no packageTypes — package headers
are ignored; QNs are bare); (b) functionTypes EMPTY → every def routes
through the methodTypes branch (extractMethod → top-level fallback to
extractFunction); (c) the val/var hook keys on the enclosing-definition
NODE TYPE walk, not the stack; (d) getSignature is LIVE (fields exist)
with the curried/type-params first-field quirk; (e) extension/given/
package_object have NO ladder branch — their leak-through behaviors are
the port's hardest part; (f) instance_expression ∈ INSTANTIATION_KINDS +
scalaBaseTypeName; (g) the scala extends branch iterates ALL supertypes;
(h) scala-only type-annotation walks (every parameters + type_parameters
context bounds) plus the hook's own emitScalaTypeRefs; (i) fn-ref spec
with bare-identifier idTypes + postfix eta unwrap; (j) imports named by
the FIRST path segment. No c/cpp-style dialect, no content sniffing:
.scala and .sc both → scala (grammars.ts:120-121; .sbt is NOT
mapped — build.sbt files are never indexed)..sc files are ordinary scala files whose top-level statements
attribute calls to the FILE node (pinned extract-script.txt: calls
println/runTop from=file, top-level val → constant with file parent).
Same for .scala files with top-level statements (grammar accepts them).filePath (hook refs via emitScalaTypeRefs carry
fromNodeId/name/kind/line/column only; extractImport sets no handledRefs;
verified across every ground-truth dump — zero refs printed a filePath).
Node decorators are likewise never set (no extractModifiers hook) — the
decorator channel is decorates REFS only.--max-deferral 0.1 HOLDS on os-lib/cats and on mainstream
Scala-2 style; sweeps over scala3-style repos need --max-deferral 0.3
(swift precedent). A deferral-rate JUMP on cats/os-lib is the bug
signal; a big number on dotty-frontier code is grammar reality.Types: functionTypes=[] (comment: "top-level function_definition is
handled via methodTypes"); classTypes=[class_definition, object_definition,
trait_definition]; methodTypes=[function_definition,
function_declaration]; interfaceTypes=[]; structTypes=[];
enumTypes=[enum_definition]; enumMemberTypes=[] (hook-handled);
typeAliasTypes=[type_definition]; importTypes=[import_declaration];
callTypes=[call_expression]; variableTypes=[] and fieldTypes=[] (hook);
extraClassNodeTypes=[]. nameField=name, bodyField=body,
paramsField=parameters, returnField=return_type. interfaceKind=trait
(unused in practice — extractInterface is unreachable, see §dispatch).
Field semantics (32 real fields — but first-match-wins bites 3×):
childForFieldName(f) returns the FIRST child carrying field f, and in this
grammar (i) several parents attach the same field to MULTIPLE children, and
(ii) anonymous tokens can carry fields (probed, brace-field.out):
braced extension (t: Int) { … } puts field body on {, the
function_definition, and } — first match is the { token with
namedChildCount 0; a for header puts enumerators on {/enumerators/}.
The walker's field lookup must scan the FULL child list (named + anonymous)
in order, exactly like tree-sitter's ts_node_child_by_field_name.
Hooks PRESENT (port each exactly):
visitNode (scala.ts:131-198) — runs for EVERY node the main walker visits (tree-sitter.ts:943-953; NOT in visitFunctionBody). Three branches:
val_definition / var_definition (:135-170) — the LIVE branch.
Name via getValVarName (:5-11): pattern field; identifier → its
text; else the pattern's first DIRECT namedChild of type identifier
(val (ta, tb) → ONE node named ta spanning the whole val; val
Some(v) → v; val multiA, multiB = 5 → multiA only — pinned,
unlike kotlin's mint-nothing destructuring); no identifier → return
false (falls through to… nothing — no other branch matches, children
recursed). Then the enclosing-definition walk up node.parent
(:146-156): first of class_definition | trait_definition |
enum_definition | given_definition | object_definition wins.
isInstanceField = class/trait/enum/given → kind field;
object_definition or NOTHING (top level, package_object, braced package)
→ val→constant / var→variable. NOTE vs kotlin: there is
NO 'local' arm — the hook never runs inside bodies (visitFunctionBody
doesn't call it), so body-local vals are handled by plain recursion
(§Body walker). lazy val → constant (modifiers don't matter).
Extra: signature = `val|var ${name}: ${typeText}` ONLY when a
type field exists (else undefined — val x = 1 has NO signature;
pinned), visibility via extractVisibility (:69-80). Then
emitScalaTypeRefs(typeNode, created.id) (:27-45): every
type_identifier in the type subtree EXCEPT SCALA_BUILTIN_TYPES
(:14-17 — Int/Long/Short/Byte/Float/Double/Boolean/Char/Unit/String/
Any/AnyRef/AnyVal/Nothing/Null) → references ref FROM THE VAL NODE at
the type_identifier's position (pinned: val SHARED_TABLE:
Map[String, Int] → references Map only — String/Int builtin-skipped;
var cb: () => Unit → nothing). Return true → dispatcher runs
scanFnRefSubtree(node, 0) and NEVER descends → top-level/class-scope
property initializers emit NO calls/instantiates refs (val topInit =
WidgetS.create(), val topLazy = compute(), val n = new Foo {…} →
nothing — pinned) — but the SCAN still captures fn-ref candidates
(§Function-refs; note the scan's nested-def halt checks functionTypes,
which is EMPTY for scala, so it descends into nested
function_definitions inside a hook-consumed val — it halts only at
lambda_expression (tree-sitter.ts:611): val fnField = (x) =>
runLam(x) captures nothing, pinned extract-edge2.txt).enum_case_definitions (:173-183) — for each direct
simple_enum_case | full_enum_case child: enum_member node named by
the case's name field, positioned at the CASE node (so case
Custom(rgb: Int) spans the params and case Earth extends Planet(5.9)
spans the extends — pinned cols in extract-torture.txt). One wrapper
per case line; case Red, Green = one wrapper, two cases. Return
true → scanFnRefSubtree; consequences: case parameters are invisible,
a case's extends_clause emits NO extends ref, and calls inside case
ctor-args emit nothing.extension_definition (:186-195) — body = childForFieldName('body')
then visit the body's namedChildren. Because field body is attached to
EACH def (and to {/} in braced form), this is a triple quirk, all
pinned (extract-ext.txt, brace-field.out):
function_definition → its
CHILDREN are visited → no node is ever minted for any extension
method; the first def's body expressions reach the ladder → its
calls emit FROM THE ENCLOSING SCOPE (file/class) at their own
positions (calls concat from=file); non-call bodies (s.length)
emit nothing.extension (t) { … }): body resolves to the { TOKEN
→ namedChildCount 0 → the whole extension is invisible (zero nodes,
zero refs — extract-ext.txt ext2).
Return true always (even when body lookup finds nothing).getSignature (scala.ts:110-117) — LIVE. params =
childForFieldName('parameters'), ret = childForFieldName('return_type');
none → undefined; sig = paramsText + (ret ? : ${retText} : ''). QUIRKS,
PRESERVE (pinned in extract-torture.txt):
def curried(a: Int)(b:
String)(implicit ord: Ordering[Int]): Int → sig (a: Int): Int.function_definition the type_parameters node carries field name
parameters and precedes the value list → def genericDef[A: Numeric,
B <: BoundT](x: A): B → sig [A: Numeric, B <: BoundT]: B;
def genericLeak[T](t: T): T → [T]: T.: Int (RichIntS::twice). Empty parens → ().
Secondary ctor def this() → ().getReturnType = extractScalaReturnType (scala.ts:56-67) —
return_type field text, trimmed: this.-prefixed (fluent this.type) →
undefined; strip \[[^\]]*\] generic args (non-greedy single pass:
List[Bar]→List), strip all \s, take last .-segment; must match
/^[A-Za-z_]\w*$/. Pinned: : WidgetS→WidgetS; : com.example.other.
Remote→Remote; : T→T (generic leak, preserve); inferred → undefined;
Unit/Nothing are NOT filtered (unlike kotlin — unitRet has
ret="Unit", pinned). Feeds matchDottedCallChain (§Frameworks).
getVisibility → extractVisibility (scala.ts:69-80) — scan direct
namedChildren of type modifiers OR access_modifier; TEXT
.includes('private') → 'private', .includes('protected') →
'protected'; default 'public'. Kotlin-style includes-on-raw-text:
private[b]/private[this] → private (pinned QualPriv). Applied to
functions/methods/classes(+objects)/enums AND (via the hook) vals/vars.
NOTE: in the real CST access_modifier sits INSIDE modifiers — the
modifiers arm is what fires; keep both arms anyway.
isAsync (scala.ts:121) — literally () => false: every function/method
carries isAsync: false.
isStatic (scala.ts:123-129) — modifiers text .includes('static') →
scala has no static keyword → always false in practice (annotations
are NOT inside modifiers in this grammar, so no kotlin-style text false
positive channel — but port the text scan, not a constant).
classifyClassNode (scala.ts:105-108) — trait_definition → 'trait',
else 'class'. So object_definition → kind class (companions/case
objects included) and trait → kind trait via
extractClass(node, 'trait') (ladder :1014-1015 — extractInterface/
interfaceKind is DEAD code for scala).
extractImport (scala.ts:200-211) — signature = trimmed full node text;
moduleName = childForFieldName('path') text. Each dotted segment is a
separate path-fielded identifier → first-match-wins → the import node/ref
is named the FIRST SEGMENT: import com.example.other.OtherClass →
name/ref com (pinned ×5 in extract-torture.txt; import single →
single; import a.b → a). The identifier/stable_identifier fallback
(:204-209) is dead (path always present). Consequence for the fn-ref gate:
importedNames = {com, single, …} — imported class simple names NEVER
enter the gate (unlike kotlin's last-segment rule — flushFnRefCandidates'
QUALIFIED_IMPORT never sees the full path because the ref name is only the
first segment).
Hooks ABSENT (the walker must NOT invent them): preParse, resolveName,
recoverMangledName, isMisparsedFunction, isConst, isExported
(undefined everywhere except the file node's literal false),
classifyMethodNode, extractPropertyName, propertyTypes, packageTypes/
extractPackage (→ extractFilePackage returns null → NO namespace node,
EVER — package headers are ignored and every top-level QN is bare; pinned),
getReceiverType (no receiver-QN surface, no owner-contains fallback),
resolveBody (body via the body FIELD everywhere), extractModifiers (no
node.decorators), extractBareCall, synthesizeMembers, skipBodilessClass
(bodiless class Foo mints a node — the :1685 comment names Scala),
methodsAreTopLevel, resolveTypeAliasKind, interfaceTypes machinery.
45a53eb)| Node | Branch | Behavior |
|---|---|---|
| every node | visitNode hook first (:943) | val/var, enum_case_definitions, extension consumed; handled → scanFnRefSubtree + STOP |
| every node | maybeCaptureFnRefs (:990) | fires for arguments/assignment_expression/val_definition (SCALA_SPEC keys) in visitNode context too |
function_definition/function_declaration |
methodTypes:1027 (functionTypes EMPTY — :994 never fires) | extractMethod:1737 → gate :1747: inside class-like → method; top level (no receiver hook, no methodsAreTopLevel, parent never object/object_expression) → falls to extractFunction:1517 → function. function_declaration = bodiless def (def m(): Int in traits/abstract classes) — same routing, no body walk |
class_definition |
classTypes:1005 → classify | 'class' → extractClass:1679. Includes case class, implicit class, abstract class |
object_definition |
classTypes:1005 | extractClass → kind class (companion objects, case object, object X extends App) |
trait_definition |
classTypes:1005 | classify 'trait' → extractClass(node, 'trait') → kind trait (extractInterface:1834 is UNREACHABLE) |
enum_definition |
enumTypes:1064 → extractEnum:1914 | §Enums |
type_definition |
typeAliasTypes:1071 → extractTypeAlias:2890 | plain type_alias node (top level AND as a class member — Outer2::Member pinned). QUIRK: the alias-value ref walk reads field 'value' → scala's field is 'type' → NO reference to the aliased type (returns false → children re-visited, nothing matches). opaque type identical |
import_declaration |
importTypes:1209 → extractImport:3170 | §Imports |
package_clause |
no branch | recursed. Header form: nothing extracted, nothing pushed — contents stay file-parented with bare QNs. Braced form package a.b { class X }: recursion reaches the members (class X → file-parented, QN X — pinned extract-misc.txt). Multiple/chained package clauses likewise ignored |
package_object |
no branch | recursed → template_body members visited with FILE on stack: defs → extractMethod → not-class-like → functions, vals → hook (enclosingDef walk finds NOTHING — package_object is not in the list) → constants/variables; all bare-QN file children (pinned: pkgHelper function, pkgShared constant) |
given_definition |
no branch | recursed. given x: T = new T {…} → instance_expression child → :1255 → instantiates from the enclosing scope + (anon-body via recursion) §instance_expression row. given T = expr → expr recursed (calls emit from enclosing scope — summonOrd() pinned). No given node is ever minted; a given's name binds nothing |
extension_definition |
hook | §Extractor config — first-def leak / braced invisibility |
call_expression (top level / template_body statements) |
callTypes:1248 → extractCall:3684 | class-body statements (require(size > 0) after the primary ctor, object Boot extends App { bootUp() }) attribute to the CLASS node — pinned extract-edge2.txt |
instance_expression |
INSTANTIATION_KINDS:1255 (:360 names scala) → extractInstantiation:4610 | §Instantiation. findAnonymousClassBody:4815 looks for class_body/declaration_list — scala's anon body is template_body → null → extractAnonymousClass NEVER runs → skipChildren stays false → children recursed: the template_body's defs hit methodTypes → extractMethod → (not class-like at top level) → anon-class methods leak out as functions/methods of the ENCLOSING scope — pinned: top-level given's compare → function compare (bare QN); given-inside-object's compare/innerVal → method/field OF the object (Registry::compare, extract-given2.txt) |
infix_expression |
no branch | recursed; left/operator/right fields. Infix calls are INVISIBLE: list map transform, a foo b, 1 :: rest, x + y, counter += 1 emit NOTHING (no calls ref, no fn-ref — pinned) |
assignment_expression |
no ladder branch; SCALA_SPEC dispatch | fn-ref rhs capture (§Function-refs); children recursed (LHS field_expression reaches extractStaticMemberRef in bodies — §Static-member) |
annotation |
no branch (child of its definition) | consumed by extractDecoratorsFor from the decorated node (§Decorators) |
lambda_expression / case_block / match_expression / for_expression / indented_block / block |
no branch | recursed transparently (in bodies via visitForCallsAndStructure) |
INSTANTIATION_KINDS others / impl_item / swift property / property_signature / export_statement (TS) |
never | not scala node kinds; the swift property branch :1121 is language-gated |
generateNodeId(filePath, kind, name,
startRow+1) = `${kind}:${sha256(`${filePath}:${kind}:${name}:${line}`).hex.slice(0,32)}`.
FILE node id = file:${filePath} (:509), name = basename, qualifiedName =
filePath, endLine = source.split('\n').length, isExported false.getChildByField(node, 'body')… note :1330 only calls
this.extractor.resolveBody?.() — hook absent → no extension ever
(scala bodies are child nodes, in-range anyway). Kernel: skip.::, file node
excluded, namespacePrefix always empty. No namespace node → top-level
QN = bare name even in a packaged file (pinned everywhere).
Companion objects: class + object are TWO class-kind nodes with the
SAME name (different start lines → different ids) and their members
share one QN namespace — WidgetS::render (class) and WidgetS::create
(companion) are indistinguishable by QN (pinned; no disambiguation —
preserve).assignment —
scala's is assignment_expression → always false (Ruby-only, dead).Route recap: ALL defs → extractMethod first. In class-like → method node.
Top level → :1747 gate fails → extractFunction (the object/
object_expression parent check :1751 never matches scala) → function node.
extras (both): docstring (§Docstrings), signature (LIVE hook — §config),
visibility, isAsync false, isStatic false, returnType (hook); isExported only
on extractFunction path → undefined (hook absent). extractTypeAnnotations
(§Type-annotation refs) then extractDecoratorsFor (§Decorators), push, body =
getChildByField(node, 'body') (block / indented_block / EXPRESSION — a
single-expression body like = new WidgetS(a) or = a + 1 is walked as the
body; pinned instantiates from topLevel), visitFunctionBody, pop.
name field. operator defs keep the operator text as the name
(+, :: — operator_identifier node; unary_- is a plain identifier;
pinned method WidgetS::+). Backtick names keep backticks
(class `Weird Name`/method `strange def` pinned).
Secondary constructors are methods named this (def this() =
this(0) → method WidgetS::this + calls ref this — pinned; the
NAME_STOPLIST only applies to fn-refs, not calls).function_declaration (bodiless def): method node minted, sig/ret intact,
no body walk (AbsS::abstractM pinned).resolvedBody = hook(absent) ?? getChildByField(node, 'body') → template_body
(braces or scala-3 colon form — same node type) / enum_body for enums. NO
skipBodilessClass → bodiless mints. extras: docstring, visibility, isExported
undefined. Then extractInheritance (§below) — extends refs precede member
emissions. extractCsharpPrimaryCtorParamRefs — csharp-gated no-op.
extractDecoratorsFor (annotations on the class — @deprecated class Old →
decorates). Push, walk body children via visitNode, pop.
class_parameters (primary ctor) child is NEVER walked: ctor params mint
no nodes, their types emit NO references, their default-value calls emit
NOTHING (pinned: WidgetS's label: String = defaultLabel() → silent;
case-class fields invisible — DataS has zero members).call_expressions → calls from the CLASS node (pinned: case class
DataS(x: Int, y: String = mkY()) → calls mkY from=class:DataS), and
extends_clause arguments recursion reaches super-ctor arg calls the same
way. Reproduce the asymmetry exactly. (The re-visited extends_clause emits
nothing extra — extends refs come only from extractInheritance; type
nodes/class_parameter children match no ladder branch.)Outer2::InnerObj::IC); template_body
STATEMENTS (calls) → extractCall from the class (§dispatch); secondary
ctor → method this.Drawable::traitVal pinned); bodiless trait defs → methods.trait X { self: Y => … }): the self-type is invisible (no
refs, no node); members extract normally (pinned extract-misc.txt).body = enum_body (REQUIRED — a bodiless enum would mint nothing; doesn't
occur). extras: docstring, visibility, isExported undefined.
extractInheritance runs (an enum's own extends_clause). Body loop
(:1941-1950): enumMemberTypes is EMPTY → every child goes through visitNode:
enum_case_definitions → hook → enum_member nodes positioned at the case
nodes (simple case = just the name extent; param/extends cases span their
tails — cols pinned); function_definition → extractMethod (enum is
class-like → Http::describe); vals → hook → fields. extractEnumMembers
(:1958) is DEAD for scala. QUIRKS, PRESERVE: case parameters
(Custom(rgb: Int)) and per-case extends (case Earth extends Planet(5.9))
are completely invisible — no field nodes, no extends refs, no calls from
ctor args (hook consumption). Enum class_parameters (enum Planet(mass:
Double)) are invisible like any bodied class's.
Hook returns {moduleName: FIRST path segment, signature: trimmed full text} →
import node (name/QN = first segment) + the generic imports ref
(:3183-3194): {fromNodeId: always the file node (no namespace),
referenceName: first segment, line/column of the import_declaration}. No
scala-specific emit pass (:3197-3234 all gated to other languages). Shapes
(CST pinned in cst-snippets.txt §imports):
import a.b.C → three path identifiers → name a.{C, D} (namespace_selectors), wildcard _/*/given
(namespace_wildcard), renames {X => Y} (arrow_renamed_identifier) /
{X as Y} (as_renamed_identifier, name/alias fields) — ALL invisible: the
name is still the first path segment; selectors/aliases bind nothing.import single (one segment) → single.Entry: not vbnet/erlang/ruby/arkts. func = getChildByField(node,
'function') ?? namedChild(0) (:4313) — scala call_expression HAS a real
function field. cpp operator recovery :4324 gated off.
Member branch (:4364) — func.type === field_expression (in the :4364
list):
value +
field). The kotlin navigation_suffix fallback is dead.func.namedChild(0)
= the value child.string
("lit".toUpperCase()) and integer_literal (5.toString()) → emit
NOTHING (pinned). Port the whole set.identifier (:4401) not in SKIP_RECEIVERS {self,this,cls,super}
→ `${recv}.${method}` (w.render, Registry.register,
obj.method). this/super receivers are plain identifier nodes
with those TEXTS → SKIP → bare methodName (this.mine() → mine,
super.hashCode() → hashCode — pinned; unlike kotlin's
this_expression path, same net effect).call_expression + scala in the gate (:4408-4418) → the #750
re-encode, scala arm (:4443-4464): innerFn = getChildByField(receiver,
'function') (a REAL field here — NOT kotlin's namedChild(0)) → text with
->→. then \s+ stripped; re-encode ONLY when /^[A-Z]/
(:4461) → `${innerCallee}().${methodName}`. Pinned:
WidgetS.create().render() → WidgetS.create().render + inner
WidgetS.create (recursion); Foo(1).bar() → Foo().bar + Foo
(companion-apply chain); lowerFactory().chain() → bare chain +
lowerFactory.field_expression (2-hop a.b.method3()) or anything else →
bare methodName (pinned).Else branch (:4518-4520) — calleeName = RAW func text: bare helper;
apply-sugar WidgetS(1) → calls ref WidgetS (capitalized bare —
resolution's CONSTRUCTS_VIA_BARE_CALL handles it, §Frameworks);
this(0) in a secondary ctor → calls this. QUIRKS, PRESERVE (pinned):
generic_function callee keeps its type args: genericCall[Int](1) →
calls genericCall[Int] (the c/cpp <-strip :4542 is gated off and
targets < anyway; deterministic garbage — reproduce byte-for-byte).curried(1)(2) → outer callee =
func (a call_expression) raw text curried(1) + inner curried
(recursion); Foo(1)(2) → Foo(1) + Foo (pinned extract-misc.txt).s"… ${w.render()} …"
→ calls w.render at the inner call's position (interpolation > block >
call recursion); $id (interpolation > identifier) emits no call (pinned).xs.map(el => …) / { el => … } / partial-fn { case q =>
… }) — arguments node is arguments/block/case_block respectively;
all recursed; inner calls attribute to the ENCLOSING function (lambdas mint
nothing).ctor = constructor/type/name fields → all null on instance_expression
(only arguments is a field on it) → namedChild(0) = the type node →
scalaBaseTypeName (:201-224): type_identifier/identifier → text;
generic_type → recurse namedChild(0) (new Monoid[Int] → Monoid);
stable_type_identifier/stable_identifier → LAST identifier segment
(new a.b.C() → C); default → first direct type_identifier child ?? null.
→ instantiates ref at the instance_expression's position (the new).
Emitted from: expression bodies (def f = new W(a) — from the function),
body statements/initializers in bodies, given RHS at any scope (from
file/class), ladder-visited statement positions. NOT emitted from
hook-consumed val initializers (val topInit = new W(…) at top/class
scope → nothing — pinned). Anonymous-class bodies: §dispatch row — never an
anon class node; members leak to the enclosing scope; no extends ref to
the instantiated type (extractAnonymousClass never runs).
Called ONLY from the body walker (:5218). field_expression ∈
MEMBER_ACCESS_TYPES (:327). Mechanics: callee-of-call skip (:4772-4778 —
Registry.register(w)'s callee nav emits nothing); recv = object/expression/
scope fields (null) ?? namedChild(0); accepted type identifier (:4792);
text /^[A-Z][A-Za-z0-9_]*$/ → references ref at the RECEIVER's
position. Pinned (extract-torture.txt StaticReads):
val c1 = Registry.count → references Registry; Http.Ok → references
Http; process(Registry.count) (argument position) → references
Registry (plus the process call).com.example.Fq.CONST_READ → NOTHING (outer recv is a field_expression;
the innermost com is lowercase).Registry.count = 5 — the
LHS field_expression is a plain child of assignment_expression, visited
by body recursion → references Registry (pinned). Preserve.decorates, args includedextractDecoratorsFor (:4897) runs for functions/methods/classes/objects/
traits/enums (NOT hook-created vals/enum members — pinned: @volatile var
→ nothing). Scala annotations are DIRECT CHILDREN of the definition node
(annotation node with fields name: type_identifier, arguments) — scan #1
(:4976-4978) hits them; consider() accepts type annotation (:4928); the
target loop finds the type_identifier name child (:4951) → name text →
<-strip + last-./::-segment normalization (:4959-4962) →
decorates ref {from: decorated node, name, line/col of the ANNOTATION
node}. Pinned: @main def entry → decorates main; @inline def fast →
inline; @deprecated("gone", "1.0") def old → decorates deprecated
(WITH-args annotations still emit — the name field precedes the arguments;
kotlin's constructor_invocation dropout does NOT reproduce). Annotation
ARGUMENT expressions are never visited (no calls refs from them). The
modifiers-descent (:4983) and backward-sibling scan (:5013) are inert for
scala (annotations are neither inside modifiers nor preceding siblings).
extractInheritance loops the DEFINITION node's direct namedChildren for
extends_clause (field extend; a direct child on class/object/trait/enum
definitions). The scala branch iterates ALL namedChildren of the
extends_clause and maps each through scalaBaseTypeName → one extends
ref per supertype {name, line/col of the supertype node}. Pinned:
extends BaseW(size) with Drawable with Ordered[WidgetS] → extends
BaseW + Drawable + Ordered (generic unwrapped; the arguments child
yields null via the default case → skipped).extends B, C → both. Single extends Shape on
objects (object Circle extends Shape) ✓ (pinned).extends Base(1)(2) (plain, parse-clean) → extends
Base only (the second arguments child → null → skipped;
extract-super2.txt).derives Show (derives_clause, field derive) emits NOTHING — not an
extends_clause; preserve the silence.implements — traits ride extends.case Earth extends Planet(5.9)) — hook-consumed,
nothing (§Enums). Anonymous new T {…} — no class node, no extends
(§Instantiation).extractTypeAnnotations (:5788) for functions/methods:
parameters child
(all curried lists — the trailing implicit list included) →
extractTypeRefsFromSubtree (:6090): every type_identifier leaf except
BUILTIN_TYPES (:5768-5782 — includes the scala block Int/Long/…/Null
PLUS the cross-language entries; note lowercase error etc. can't occur)
→ references ref at the leaf. The type_parameters node also carries
field name parameters but is NOT type parameters → not matched by
this walk (it's matched by walk 3).getChildByField('return_type') subtree → refs
(generic return Option[A] → Option + A).type_parameters child → subtree refs — context/upper bounds emit
([A: Numeric, B <: BoundT] → Numeric + BoundT; the declared names
A/B are identifier nodes → silent). Pinned order for genericDef:
params-walk refs (A) → return (B) → bounds (Numeric, BoundT).type_annotation direct-child search (:5873) — no such node kind in
scala → dead.Plus the hook's emitScalaTypeRefs on val/var type ascriptions
(§Extractor config — its OWN builtin set SCALA_BUILTIN_TYPES, which lacks
the cross-language entries; val m: Monoid[Int] → references Monoid).
extractVariableTypeAnnotation (:6074) needs a type_annotation child → dead;
the body-walker variable_declarator branch (:5230) — no such kind → dead.
Net: param/return/bound types on defs + ascribed val/var types emit;
class_parameters types (bodied classes), local val types in bodies (no hook
there, no variable_declarator), and pattern types (case ws: WidgetS) emit
NOTHING. Class primary-ctor types are the notable coverage hole — preserve
it.
Scala comment node kinds: comment (//) and block_comment (/* */ and
Scaladoc /** */). BOTH are in getPrecedingDocstring's accepted set
(:110-115) — unlike kotlin, Scaladoc survives, and block/line runs CHAIN in
either order. cleanCommentMarkers (:77-90): /*-open strip + per-line *
continuation strip + // strip. Pinned (extract-docs.txt): Scaladoc →
"Scaladoc kept?\nsecond line with star"; // runs joined with \n;
/** block */ + // trailing → "block doc\ntrailing line"; a blank
line does NOT break the chain (// detached two lines above still
attaches — previousNamedSibling skips whitespace); hook-created vals get NO
docstring (docVal pinned) but class/method/function/trait/enum/type_alias
do. DOCSTRING_WRAPPER_TYPES contains no scala kinds → no climbing.
CRLF: multi-line block comments keep a stray \r at each internal line
break ("Scaladoc kept?\rsecond line with star" — pinned
extract-docs-crlf.txt diff) — the JS ^/m-matches-after-\r semantics
(#1329): call the shared js_multiline_strip in docstring.rs, port
nothing. Line-comment runs are CRLF-clean (per-comment trim eats the
\r).
Port the full machinery (crib kotlin.rs): CODEGRAPH_VALUE_REFS=0 kill;
MAX_VALUE_REF_NODES 20,000; isGeneratedFile skip.
/[A-Z_]/, parent id prefix ∈ {file:, class:, module:, struct:,
enum:}. Because scala mints NO namespace node, top-level constants ARE
targets (kotlin's namespace-drop quirk does NOT apply — TOP_LIMIT
pinned with a class-method reader). Object/class/trait members (class:
parent) are targets; count (no uppercase/underscore) is not (pinned).
Same-name targets: LAST registration wins the map slot — with
Config.TIMEOUT_MS and Config2.TIMEOUT_MS in one file, readers of
EITHER get an edge to Config2's node (fileScopeValues.set overwrite —
pinned vref2.out; wrong-target quirk, PRESERVE).val_definition/var_definition (:838-843) — pattern field, bump
ONLY when the pattern IS an identifier (tuple/case-class patterns bump
nothing → a destructured local shadow never prunes). bump() counts
identifier nodes (:807). Every val/var ANYWHERE (the target's own
declarator + body locals — the hook never sees body locals but the prune
DFS does) — pinned: companion-ish Config.RETRY_MAX + method-local
val RETRY_MAX = 9 → RETRY_MAX pruned (readBoth emits only TIMEOUT_MS).
The kotlin/swift property_declaration case and Dart/Pascal cases are
null paths here.identifier (:907 — simple_identifier/constant/name
never occur in scala trees). Reads through Config.TIMEOUT_MS member
positions count (the member half is an identifier). String
interpolations: BOTH $CONST and ${CONST} count (interpolation >
identifier / > block > identifier — pinned readInterp ×2; kotlin's
$X-inert quirk does NOT apply). Skip self/same-name, dedupe per
(scope,target) → EDGE {kind:'references', metadata:{valueRef:true}},
appended after the walk (§Emission order). The Dart/Pascal
next-sibling pull (:891) is inert (scala bodies nest).idTypes = {identifier} (bare identifiers ARE candidates — unlike
kotlin). dispatch: arguments → args; assignment_expression → rhs field
right; val_definition → varinit field value (var_definition is
ABSENT — var cb = other initializers are NEVER captured, pinned
extract-fnref.txt). unwrap: postfix_expression → null field → first
namedChild (eta-expansion handler _ → handler, explicitRef=true).
No layers/special/ungatedModes/addressOfOnly. NAME_STOPLIST applies
(this, null, …).
val x = fn IS captured;
the nested-def halt checks functionTypes=[] so only lambda_expression
halts it — val fnField = (x) => runLam(x) captures nothing, pinned).extract-fnref.txt, extract-fnref2.txt,
extract-edge2.txt): args bare id register(handler) ✓; args eta
registerEta(other _) ✓; body varinit val stored = alpha ✓; class-scope
varinit val topStored = handler ✓ (from the CLASS node); assignment rhs
obj.cb = beta ✓; named-argument wire(cb = cbTarget) — parses as an
assignment_expression inside arguments → rhs capture ✓ (at the rhs id's
position) with the param-forward skip when cb = cb (lhs tail == rhs
text, :430-443); list List(delta) inside a hook-consumed val ✓ (args
mode via arguments).fn.member values (field_expression — no special), infix
positions (list map transform — no dispatch key), var initializers,
lambda bodies under hook-consumed vals.register(missing) drops but
same-file names pass) ∪ importedNames — which for scala is first path
segments only (§Imports; effectively: cross-file callables never pass
the gate). No ::/this. always-flush forms are ever produced. Dedupe
${fromNodeId}|${name} (pinned: three same-scope handler captures → one
ref). referenceKind function_ref (wire code 200).call_expression → extractCall (:5143), children recursed after.instance_expression → :5145 extractInstantiation; findAnonymousClassBody
→ null (template_body) → NO return → children recursed → anon-body defs
are NOT dispatched here (functionTypes empty, methodTypes not checked in
this walker) → new Ordering[Int] { def compare … } in a body: the
instantiates ref only; compare mints NOTHING and its body's calls
attribute to the enclosing method (pinned).def innerFn(k) = … inside a body is recursed-through; its calls
attribute to the ENCLOSING method (pinned: innerFn/lam/helperCall
all from CallSites::localHost). This is the inverse of kotlin (which
minted local fns) — preserve.class LocalClass/object LocalObj/trait →
FULL extractClass (kind class/trait) contained by the enclosing method,
members extract normally (pinned QNs CallSites::localHost::LocalClass::lm).
enumTypes (:5268) likewise. typeAliasTypes NOT checked → body-local
type X = … is invisible.val local = compute() → calls compute — pinned)
and no local nodes are minted. Match/for/try/if recursed transparently
(case_clause pattern Some(n) emits nothing; for … yield combine(x,y)
emits the call — pinned).extract-uni.txt: 😀 counts 2, é counts 1), as are
startIndex/endIndex substrings (getNodeText) and the import-signature
trim.extract() wrap (:454-577): file node → (no namespace) → walk →
flushFnRefCandidates → flushValueRefs → pop. Table order: nodes in
creation order; contains edges interleaved; value-ref EDGES appended
LAST (pinned — after all contains); walk-order refs, then function_ref
refs at flush (pinned last in every dump). Store/harness are
rowid-order-sensitive — reproduce exactly.\s+ handles \r, JS/Rust
parity holds) and the visibility/isStatic .includes (single-token). The
shared regexes that fire: extractCall's parenthesized-conversion (:4530),
decorator name normalization (:4959-4962), and cleanCommentMarkers' gm
strips → js_multiline_strip in docstring.rs (#1329), call it (the
block-comment \r retention is the observable — §Docstrings). Grammar
CRLF probed clean (§Grammar prep). extract-torture.txt vs
extract-torture-crlf.txt diff is EMPTY (torture's only multi-line
comment is //-run based… its scaladoc is single-line); the docs fixture
carries the multi-line pin.has_error() → defer: — including phantoms
(§Grammar prep (a): flag-true with zero ERROR nodes is COMMON in scala-3
code; trust the flag). wasm recovery is canonical.conf/routes /
*.routes files (NOT .scala — they ride the no-grammar path with
language 'yaml'); route nodes carry literal ids
(route:${filePath}:${line}:${method}:${routePath}) and language:
'scala'; resolve() maps Controller.method handler refs via
getNodesByName(className) (kind class) + getNodesInFile(cls.filePath)
(kind method|function, exact name). Walker obligations: class/method
node names, kinds, filePath — nothing else.Foo.create().bar re-encode + the inner method's
returnType (extractScalaReturnType's product — why Unit is left
unfiltered doesn't matter here, but the last-segment/generic-strip rules
do). CHAIN_LANGUAGES (resolution/index.ts:42) includes scala (the ().
split).Foo(args).method()
receivers resolve as class Foo (companion-apply convention).`\bval lg = (new )?Logger` / `\blg: Logger` regexes. Walker
obligations: accurate node startLine/endLine spans + language tag.`Weird Name`) and operator glyphs (+, ::); import
nodes are named the FIRST path segment; enum_member line/column = the CASE
node's (which for simple cases equals the identifier); companion pairs
collide on (kind,name) but differ on line. node_ids dedupe-vec pattern
still required — same-(kind,name,line) CAN collide (one-line
class X { … }; object X constructions, secondary-ctor this twins).path, def parameters, extension/for body/enumerators (§config).
Implement child_by_field exactly once, tree-sitter-faithful.uni.scala (surrogate-pair emoji counts 2)./** … */ (the \r
retention pin) and a Scala-3 indented file (scanner CRLF).has_error() → defer: including phantoms; sweep
defaults --max-deferral 0.1 for os-lib/cats-style repos, 0.3 for
scala3-style (§arch-6).GRAMMAR_LANGUAGES += 'scala'
(kernel-grammar-parity.test.ts:39) must show ABI 15 / 26650 states /
32 fields / identical kind+field tables vs the production wasm.## Fixtures to build, exercised by a new
__tests__/kernel-scala-parity.test.ts.scripts/kernel-parity.mjs <dir>, order-sensitive
full-object):
…/scratchpad/svy-scala/gate-repos/os-lib (small, 59 files, expect 0
deferrals)…/gate-repos/cats (medium, 835 files, expect ~15 deferrals ≈1.8%)…/gate-repos/scala3 — sweep compiler/src (577 files, ~57 deferrals
≈10%, --max-deferral 0.3) and library/src (652 files, ~116 ≈18%,
the phantom-heavy set) rather than the whole repo (tests/ is
deliberately-invalid input — fine for a robustness pass, useless as a
parity denominator).
(cloned fresh at survey; re-clone public OSS if gone — agent-eval policy.)
Expect 0-diff on every NON-deferred file. Then full-init dump-diffs
byte-identical (kernel arm vs CODEGRAPH_KERNEL=0, dump-graph.mjs,
cmp) on all three..sc fixture +
defer fixtures (§Fixtures 5-6) ×2 green with CODEGRAPH_KERNEL_EXPECT=1.DEFAULT_ROUTED += 'scala' (kernel/index.ts:37 list) only after ALL of
the above; changelog rides the existing kernel entry.__tests__/fixtures/kernel-parity/torture.scala — seed from
svy-scala/torture.scala (its extract-torture.txt is the expected-output
pin: 80 nodes / 82 edges / 75 refs). Inventory: package header (ignored, no
namespace); imports ×5 (first-segment names; selectors/wildcards/renames);
scaladoc + //-run docstrings; top-level defs (function kind) incl.
curried (first-list sig), generic (type-params-first sig + bound refs),
inferred/Unit/qualified/generic-leak returns, private, @main/
@deprecated(args) decorates; top-level val/var/lazy/tuple/pattern
destructuring (first-identifier names) + ascribed types (refs) +
initializer-invisibility; bodied class with primary ctor (params
invisible), fields (val/var/private/annotated), secondary ctor (this
method + this call), methods incl. operator +, member calls, extends
with-chain; companion object (second class:WidgetS + shared QN space +
DEFAULT_SIZE value-ref target read by both create/readDefault); case
class bodiless-header asymmetry (mkY() call from class) vs bodied twin;
case object extends; abstract class + function_declaration; trait with
default method + val field; sealed trait + object/class extends; enum
(simple/param/extends cases at case-node positions, invisible tails,
method); enum with class_parameters; given → instantiates + leaked
function; anonymous given; extension (first-def call leak, second-def
invisibility) + single-def extension; implicit class; type aliases (no
value refs) + opaque; call shapes: bare, apply-sugar WidgetS(1),
member, chained cap re-encode + inner, lower chain, 2-hop, this/super
bare, literal receivers (nothing), genericCall[Int] bracket callee,
curried-call raw inner, new in expression body, anon new … {} in body
(instantiates + leaked-nothing), interpolation call + $id, eta + bare +
named-arg fn-refs, infix invisibility, match/for; static reads
(Registry.count, Http.Ok, com.example.Fq.X nothing, write LHS
EMITS, callee skip); local defs (innerFn invisible, LocalClass/LocalObj
full); package object (flattened); UTF-16 line. Keep it parse-clean
(avoid given … with {}, end identifiers, curried+generic super-args).\r pin) and an indented Scala-3
fixture (external-scanner CRLF — indent.scala seed, extraction
byte-identical pinned)..sc fixture — top-level statements → calls from FILE; file-parent
value-ref target (script.sc seed).given2.scala +
ext-probe.scala seeds (the leak asymmetries are the port's likeliest
regression site).def f(x: List[Int]^): Int = 1 — kernel
defers on has_error() despite a complete, ERROR-free CST; wasm output
byte-normal.given x: C with { … } or
end-identifier file — kernel defers; wasm-served output pinned.vref.scala seed: object + top-level targets, shadow
prune, $X/${X} interpolation reads, lowercase non-target, the
same-name last-wins mis-target (Config/Config2 TIMEOUT_MS →
Config2's node).fnref.scala/fnref2.scala seeds: all five capture
channels + var-init non-capture + dedupe + register(missing) gate drop.svy-scala/)cst-probe.cjs + snippets.json + cst-snippets.txt (20 snippet CST dumps
WITH per-node childForFieldName truth tables), brace-field.out (the
anonymous-token field proof: extension {-body, for enumerators),
extract-probe.cjs (runs the REAL dist extractor) + ground-truth dumps
extract-{torture,torture-crlf,docs,docs-crlf,vref,fnref,fnref2,misc,indent,
indent-crlf,script,given2,edge2,ext,super2,imp2,uni}.txt (pinned outputs
cited throughout; double as walker test expectations), vref2.out
(value-ref target identity), crlf-cst.cjs (LF/CRLF s-exp equality ×7),
error-sweep.cjs + errors-{os-lib,cats,scala3,scala3-compiler,
scala3-library,scala3-tests}.txt, err-sample.cjs + err-samples.out
(error-class contexts), errvariants.out (17 minimal shapes),
phantom-scan.cjs + phantom-scan-{lib,cats}.out + phantom-min.out (the
^-postfix one-liner), fixtures (torture.scala, docs.scala,
vref.scala, fnref.scala, fnref2.scala, misc.scala, given2.scala,
edge2.scala, ext-probe.scala, indent.scala, uni.scala,
script.sc, …), tsscala-clone/ (the 0aca5d0a6f checkout) +
grammar-shas.txt, gate-repos/{os-lib,cats,scala3}. Scratch is
throwaway — re-derive from this doc if gone.