feat: add F# extractor (.fs/.fsx) via ionide tree-sitter-fsharp - #3221
feat: add F# extractor (.fs/.fsx) via ionide tree-sitter-fsharp#3221pcshrosbree wants to merge 12 commits into
Conversation
Modules, namespaces, record/union/class types with union cases and members, let-bound functions/values, open imports, and calls including pipeline application (x |> f, f <| x). Optional [fsharp] extra. .fs/.fsx join the dotnet interop family (extract/analyze/build maps) so F#->C# references rewire across the seam instead of dangling. Follows extractors/ocaml.py: sourceless ref stubs (Graphify-Labs#1402), local-def ambiguity tracking, two-pass call resolution, qualified-call gating. Registration is covered by tests/test_fsharp_registration.py, one named test per wiring point; each entry was mutation-checked (removed in turn, confirming a test fails). Graded on a real mixed Rust/C#/F# repo: 100 files, 2361 sourced nodes, 10238 call edges, 20/20 sampled call edges verified at their cited source lines. Known limits: .fsi signature files not wired (language_signature() is a follow-up); computation expressions and #r/#load directives deferred.
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.
Graphify review — findings
Adds an F# extractor (extract_fsharp) behind an optional [fsharp] extra using ionide's tree-sitter-fsharp, pulling modules, namespaces, record/union/class types with union cases and members, let-bound functions/values, open imports, and calls including pipeline application (x |> f, f <| x); it follows the OCaml extractor's sourceless ref-stub and two-pass call-resolution discipline, and returns an empty result with an error when the grammar isn't installed. Wires .fs/.fsx into the dispatch table, code-extension set, per-extension extra mapping, and the dotnet/cs language families so F#→C# references rewire across the seam.
Worth a look
- First named child of application_expression not treated as callee —
graphify/extractors/fsharp.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Same-named members in different F# types are merged into one node —
graphify/extractors/fsharp.py:189· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Qualified-call test passes if the call edge is missing entirely —
tests/test_fsharp.py:119· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3364 functions depend on the 701 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 523 callers, 43 callees - new:
_rebuild_code()— 113 callers, 50 callees - new:
build_from_json()— 192 callers, 18 callees - new:
detect()— 108 callers, 15 callees - new:
build_merge()— 62 callers, 13 callees - new:
to_obsidian()— 36 callers, 13 callees - new:
save_manifest()— 40 callers, 11 callees - new:
to_json()— 54 callers, 7 callees - …and 86 more — each is listed as a finding
Verification — 3364 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 2954 function(s) in the blast radius were not formally verified this run
· 4 grounded finding(s) anchored inline below; 90 more finding(s) on lines outside this diff (see the check run).
| _PIPE_LEFT = frozenset({"<|", "<||", "<|||"}) # callee on the left | ||
|
|
||
|
|
||
| def extract_fsharp(path: Path) -> dict: |
There was a problem hiding this comment.
extract_fsharp()
fans out to 7 callees (efferent coupling); 12 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| parts = identifiers_of(tn) | ||
| return (parts[-1] if parts else None), line_of(tn) | ||
|
|
||
| def emit_union_cases(defn, type_nid: str) -> None: |
There was a problem hiding this comment.
emit_union_cases()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| add_edge(type_nid, cnid, "contains", line_of(case)) | ||
| register_def(cname, cnid) | ||
|
|
||
| def emit_member(member_defn, type_nid: str) -> str | None: |
There was a problem hiding this comment.
emit_member()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return node | ||
| return None | ||
|
|
||
| def walk(node, container_nid: str, enclosing_value: str) -> None: |
There was a problem hiding this comment.
walk()
fans out to 15 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
…test Both from graphify's own review of this PR: - emit_member keyed nodes by (file, member) only, so two types in one file each having e.g. Dispose merged into a single node. Ids now carry the owning type's label; on a real mixed corpus this un-merged 18 member nodes. New test: two same-file types with a Dispose member must yield two nodes. - test_qualified_external_call_stays_distinct asserted only inside a loop over matching edges, so it passed vacuously when no call edge was emitted at all. It now first asserts the edge exists.
|
Thanks for the review — responding to all three findings (addressed in 1129f64): Same-named members in different F# types merged into one node — confirmed, fixed. Qualified-call test passes if the call edge is missing entirely — confirmed, fixed. The test asserted only inside a loop over matching edges — vacuously green when no edge was emitted at all. It now first asserts the edge exists, then checks it stays a sourceless stub. Fair catch: I mutation-checked the registration points but not this test's own assertions. First named child of application_expression not treated as callee — assessed, no change, reasoning below; happy to be corrected with a concrete case. The extractor records a callee only when All 21 F#-related tests green; corpus spot-check re-run after the fix (20/20 sampled call edges verified at their cited source lines). |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.
Graphify review — findings
Adds an F# extractor (extract_fsharp) behind an optional [fsharp] extra using ionide's tree-sitter-fsharp, pulling modules, namespaces, record/union/class types with cases and members, let-bound functions/values, open imports, and calls including pipeline forms (x |> f, f <| x); when the grammar isn't installed it returns empty nodes/edges with an error string rather than failing. Registers .fs/.fsx across detection (CODE_EXTENSIONS), dispatch, and the language-family maps so they extract like source and join the dotnet interop family, letting F#→C# references rewire across the seam. Following the OCaml extractor's discipline, cross-file call targets emit sourceless ref-stubs so the corpus rewire can collapse them onto the real definition.
Worth a look
- Type node IDs ignore enclosing modules and collapse same-named types —
graphify/extractors/fsharp.py:256· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3365 functions depend on the 702 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 523 callers, 43 callees - new:
_rebuild_code()— 113 callers, 50 callees - new:
build_from_json()— 192 callers, 18 callees - new:
detect()— 108 callers, 15 callees - new:
build_merge()— 62 callers, 13 callees - new:
to_obsidian()— 36 callers, 13 callees - new:
save_manifest()— 40 callers, 11 callees - new:
to_json()— 54 callers, 7 callees - …and 86 more — each is listed as a finding
Verification — 3365 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 2955 function(s) in the blast radius were not formally verified this run
· 4 grounded finding(s) anchored inline below; 90 more finding(s) on lines outside this diff (see the check run).
| _PIPE_LEFT = frozenset({"<|", "<||", "<|||"}) # callee on the left | ||
|
|
||
|
|
||
| def extract_fsharp(path: Path) -> dict: |
There was a problem hiding this comment.
extract_fsharp()
fans out to 7 callees (efferent coupling); 13 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| parts = identifiers_of(tn) | ||
| return (parts[-1] if parts else None), line_of(tn) | ||
|
|
||
| def emit_union_cases(defn, type_nid: str) -> None: |
There was a problem hiding this comment.
emit_union_cases()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| add_edge(type_nid, cnid, "contains", line_of(case)) | ||
| register_def(cname, cnid) | ||
|
|
||
| def emit_member(member_defn, type_nid: str, owner_label: str = "") -> str | None: |
There was a problem hiding this comment.
emit_member()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return node | ||
| return None | ||
|
|
||
| def walk(node, container_nid: str, enclosing_value: str) -> None: |
There was a problem hiding this comment.
walk()
fans out to 15 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
…nums Ten findings from an adversarial review pass, each verified against the live grammar before fixing: - Dotted callees (application > dot_expression): `Grasp.Telemetry.init args` and method-on-expression calls now record call edges (+381 raw call edges on a real 100-file corpus). Pipe operands accept them too. - `let rec f ... and g ...`: every and-joined head is minted and body calls attribute to their own binding (no false f->f self-loop, no vanished g). - Namespaces are canonical: `namespace Grasp.Core` emits the same csharp_namespace:<sha1> node id as the C# path, with type: "namespace", so N files merge into one hub, the canonicalize pass applies, and the unique-stub rewire skips them. Namespace segments no longer whitelist local qualified-call binding (corpus-wide names must stay stubs). - Members: labels use the dotnet `.Name()` method convention so the rewire treats them as methods, not type-like unique-stub targets. - Union cases are type-scoped (two same-file `Ok` cases stay distinct) and the single-case wrapper `type Email = Email of string` no longer emits a contains self-loop. - Enum members (enum_type_cases) are emitted; previously only DU cases. - A comment between pipe operand and operator no longer kills the edge. - Destructuring `let (a, b) = ...` mints one definition per bound name. - `.fs` dispatch sniffs for GLSL fragment shaders (mirrors the .m ObjC/MATLAB sniff) instead of ERROR-parsing them into dotnet stubs. - Registration tests no longer module-level-skip the pure-literal registry guards on machines without the grammar. One regression test per finding; 30 F# tests total, all registration mutations still killed, 20/20 sampled corpus call edges verified at their cited lines.
|
Second hardening pass pushed in 4a3c419 — an independent adversarial review over the diff surfaced ten further findings; each was verified against the live grammar with AST probes before fixing, and each carries a regression test:
State after this commit: 30 F#-specific tests, every registration mutation-killed, full suite green apart from the two failures that reproduce on clean |
There was a problem hiding this comment.
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds an optional F# extractor (extract_fsharp, [fsharp] extra via ionide tree-sitter-fsharp) for .fs/.fsx files, capturing modules, namespaces, record/union/class/enum types with union cases and members, let-bound functions and values, open imports, and calls including pipeline application (x |> f, f <| x), with let rec ... and heads all minted for forward-reference resolution and cross-file targets kept as sourceless ref-stubs. Registers .fs/.fsx in the code-extension set, dispatch table, and the dotnet/cs interop families so F#→C# references rewire across the seam, minting namespaces on the canonical csharp_namespace: id and members as type-scoped .Name() labels to match the C# rewire keys. Guards .fs with _looks_like_fsharp_source, which sniffs the file head and declines routing GLSL fragment shaders or Forth to the F# extractor rather than ERROR-parsing them into bindable dotnet stubs.
No blocking issues surfaced. 13 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3380 functions depend on the 717 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 523 callers, 43 callees - new:
_rebuild_code()— 113 callers, 50 callees - new:
build_from_json()— 192 callers, 18 callees - new:
detect()— 108 callers, 15 callees - new:
build_merge()— 62 callers, 13 callees - new:
to_obsidian()— 36 callers, 13 callees - new:
save_manifest()— 40 callers, 11 callees - new:
to_json()— 54 callers, 7 callees - …and 87 more — each is listed as a finding
Verification — 3380 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 2970 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify \_get\_extractor.
The verifier did not have enough to check \_get\_extractor, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
· 6 grounded finding(s) anchored inline below; 89 more finding(s) on lines outside this diff (see the check run).
| return any(m in head for m in fsharp_markers) | ||
|
|
||
|
|
||
| def _get_extractor(path: Path) -> Any | None: |
There was a problem hiding this comment.
_get_extractor()
fans out to 7 callees (efferent coupling); 28 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| _CALLEE_TYPES = frozenset({"long_identifier_or_op", "dot_expression"}) | ||
|
|
||
|
|
||
| def extract_fsharp(path: Path) -> dict: |
There was a problem hiding this comment.
extract_fsharp()
fans out to 8 callees (efferent coupling); 22 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| parts = identifiers_of(tn) | ||
| return (parts[-1] if parts else None), line_of(tn) | ||
|
|
||
| def emit_cases(defn, type_nid: str, type_name: str) -> None: |
There was a problem hiding this comment.
emit_cases()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| add_edge(type_nid, cnid, "contains", line_of(case)) | ||
| register_def(cname, cnid) | ||
|
|
||
| def emit_member(member_defn, type_nid: str, owner_label: str = "") -> str | None: |
There was a problem hiding this comment.
emit_member()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return out | ||
| return [] | ||
|
|
||
| def mint_binding_head(head, container_nid: str) -> str | None: |
There was a problem hiding this comment.
mint_binding_head()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| call_sites.append((caller, callee, qualifier, | ||
| ".".join(parts), line_of(callee_node))) | ||
|
|
||
| def walk(node, container_nid: str, enclosing_value: str) -> None: |
There was a problem hiding this comment.
walk()
fans out to 15 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Nine distinct defects from a four-arm adversarial panel (Codex + three
Claude models), every finding independently reproduced against the live
grammar before fixing:
- Generic type names: `type Box<'T>` minted a node labelled "T" (the
last type parameter) — and a `when 'T :> IDisposable` constraint
minted a sourced IDisposable, reopening the round-2 hazard on a new
path. The type's name now comes from type_name's long_identifier child
only.
- `type X with` (type_extension) no longer mints a SOURCED node for the
possibly-foreign type it augments (an extension of System.String could
absorb every String reference in the corpus rewire; reproduced
end-to-end with a C# consumer). Members attach to a sourceless stub.
- Heritage edges: `inherit Base()` -> inherits, `interface I with` ->
implements (INFERRED, to stubs) — previously nothing was emitted, so
the rewire's supertype guard could never protect F# base types.
- Object-expression members ({ new IFoo with ... }) are anonymous: no
minted member, body calls attribute to the enclosing binding, and
local_defs is no longer poisoned into false ambiguity with real
same-named bindings.
- Active patterns ((|Even|Odd|)) and operator definitions ((+.)) mint
nodes with their delimited spelling; body calls attribute to them, not
the enclosing module.
- `member val` auto-properties are emitted (property_or_ident sits
directly under member_defn, no method_or_prop_defn wrapper).
- Definition ids chain from their container (C#'s parent-id convention)
with kind tags: sibling modules' same-named `run` bindings and the
companion type Config / module Config idiom stay distinct nodes.
- Function labels use the engine `name()` convention, excluding F#
functions from `_is_type_like_definition`'s unique-stub TYPE rewire
(a Python `parse()` stub could previously bind to an F# `parse`);
`_node_label_key` strips punctuation so cross-file matching holds.
- `open A.B.C` mirrors `_import_csharp`: EXTRACTED `imports` edge from
the file node to the full-FQN id with target_fqn metadata — the old
last-segment stub let `open System.Text` rewire onto any class named
`Text` (reproduced).
- GLSL sniff hardened (void main(, vec2/3/4, fragColor, sampler2D; bare
`//` dropped from the F# markers) — a modern no-#version shader parsed
as F#; and the guard test is now mutation-killable (its shader carries
an F# marker inside a comment, so deleting the GLSL check flips it).
40 F# tests, all registration mutations killed, 20/20 sampled corpus
call edges verified at their cited lines; heritage edges now appear on
the real corpus (5 inherits / 3 implements from F# sources).
|
Round 3 pushed in e4b1546 — a four-arm adversarial panel (Codex CLI + three Claude models, identical brief, each instructed to execute probes rather than speculate and to tag VERIFIED vs SUSPECTED) over the round-2 head. Nine distinct defects survived cross-verification; every one was independently reproduced against the live grammar before fixing, and each carries a regression test. The highlights, in severity order:
State: 40 F#-specific tests; every registration mutation-checked and killed; 20/20 randomly sampled corpus call edges verified at their cited |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds a first-class F# extractor behind an optional [fsharp] extra (ionide tree-sitter-fsharp) that pulls modules, namespaces, record/union/class types with union cases and members, let-bound functions/values, open imports, and calls including pipeline application, and folds .fs/.fsx into the dotnet language family so F#→C# references rewire across the seam. Uses canonical namespace hubs, container-chained ids, and sourceless ref stubs following the OCaml/C# resolution discipline, and emits inherits/implements heritage edges to protect base types during the corpus rewire. Guards the ambiguous .fs extension with a _looks_like_fsharp_source sniff so GLSL fragment shaders and Forth files fall through to no-extractor rather than parsing into bogus dotnet stubs.
Worth a look
- F# sniff false-negative for files lacking recognized markers in first 4KB —
graphify/extract.py:5607· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- GLSL shader misclassified as F# via generic markers —
graphify/extract.py:5620· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Generic F# regression test has no assertions —
tests/test_fsharp.py:286· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3391 functions depend on the 728 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 523 callers, 43 callees - new:
_rebuild_code()— 113 callers, 50 callees - new:
build_from_json()— 192 callers, 18 callees - new:
detect()— 108 callers, 15 callees - new:
build_merge()— 62 callers, 13 callees - new:
to_obsidian()— 36 callers, 13 callees - new:
save_manifest()— 40 callers, 11 callees - new:
to_json()— 54 callers, 7 callees - …and 87 more — each is listed as a finding
Verification — 3391 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 2981 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify \_get\_extractor.
The verifier did not have enough to check \_get\_extractor, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
· 6 grounded finding(s) anchored inline below; 89 more finding(s) on lines outside this diff (see the check run).
| return any(m in head for m in fsharp_markers) | ||
|
|
||
|
|
||
| def _get_extractor(path: Path) -> Any | None: |
There was a problem hiding this comment.
_get_extractor()
fans out to 7 callees (efferent coupling); 30 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| _CALLEE_TYPES = frozenset({"long_identifier_or_op", "dot_expression"}) | ||
|
|
||
|
|
||
| def extract_fsharp(path: Path) -> dict: |
There was a problem hiding this comment.
extract_fsharp()
fans out to 9 callees (efferent coupling); 30 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return [], line_of(tn) | ||
| return identifiers_of(name_node), line_of(tn) | ||
|
|
||
| def emit_cases(defn, type_nid: str) -> None: |
There was a problem hiding this comment.
emit_cases()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| add_edge(type_nid, cnid, "contains", line_of(case)) | ||
| register_def(cname, cnid) | ||
|
|
||
| def emit_member(member_defn, type_nid: str) -> str | None: |
There was a problem hiding this comment.
emit_member()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return out | ||
| return [] | ||
|
|
||
| def mint_binding_head(head, container_nid: str) -> str | None: |
There was a problem hiding this comment.
mint_binding_head()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| call_sites.append((caller, callee, qualifier, | ||
| ".".join(parts), line_of(callee_node))) | ||
|
|
||
| def walk(node, container_nid: str, enclosing_value: str) -> None: |
There was a problem hiding this comment.
walk()
fans out to 16 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
…ractor fixes Round 4 ran a redesigned process: a mechanical 58-construct coverage oracle built FIRST (tests/fsharp_coverage_oracle.py — presence AND commission expectations, forbidden-label sets, self-loop checks), a frozen corpus baseline for differential reconciliation, five review arms with differentiated lenses (corpus-integration, end-to-end pipeline, test-adequacy/mutation, grammar-coverage, plain-brief control), a cross-examination pass on contested fix shapes, and fixer!=verifier confirmation of each fix against its original repro. 13 verified findings folded: - GLSL/F# sniff redesigned as evidence-tiered, comment-stripped, line-anchored, 64KB window: the round-3 version DROPPED real F# whose comments mention "uniform distribution"; the first redesign was killed in cross-examination by a real corpus line starting `float count /` — weak type-keyword evidence no longer overrides an F# declaration. - .NET imports repoint now runs for pure-F# corpora (the resolver is language-agnostic but was gated on .cs presence; every F#-only repo silently lost its open edges in build's dangling prune). The C#-shaped TYPE-reference resolver stays gated: F# nodes do not yet carry its metadata contract — documented follow-up, which is also what cross-language constructor-call resolution needs. - Object-expression `references` edge was dead code (wrong child type; zero hits on 11 corpus object expressions) — now fires, with a positive test. - Generic heritage: `inherit Base<'T>()` / `interface IComparer<'T>` wrap in generic_type and were silently skipped; the shared type-reference reader now applies the same name-child rule as the round-3 type_name fix (2 live corpus recoveries). - `static let` bindings in classes mint and resolve (previously invisible; member calls to them dangled on rewireable stubs). - Class-internal `let capacity` no longer case-folds onto member `.Capacity()` (member ids carry a kind tag) — the real corpus had lost RingBuffer's public member and gained a false self-loop. - Composition operators `>>`/`<<` record both operands as callees (deliberately not generalized to custom operators: bind vs Kleisli differ on which operand is data). - `inherit Base(mkArg ())` constructor-argument calls are walked. - Partial active patterns keep their spelling: `(|Int|_|)`, not `(|Int|)`. - The round-2 annotated-let regression test was proven vacuous by mutation (its source parses down the other branch); a single-arg variant now exercises bound_value_names, whose naive reversion changed 546 corpus edges while all tests stayed green. - Positive coverage for local_containers qualified-call binding and abstract-member emission (both features previously deletable with every test green), heritage-confidence pin, comment-only-marker sniff regression test. - Dead code removed: operator register_def (nothing resolves by operator spelling), unreachable type_extension in _TYPE_DEFN_KINDS. 56 F# tests + 58/58 oracle constructs; all registration mutations killed; full suite 5,354 passed / 0 failed; corpus differential vs the frozen baseline reconciled row-for-row (+27/-9: generic implements recoveries, references edges appearing, collision un-merges, partial-AP relabels); 20/20 sampled call edges verified at cited lines.
|
Round 4 pushed in fc71a97 — this round ran a redesigned review process, and both the process and its yield are worth a maintainer's minute. Process: (1) a mechanical coverage oracle built before any review — Yield: 13 verified findings, zero false claims across the panel. Highlights:
State: 56 F# tests + 58/58 oracle constructs; every registration and new guard mutation-killed; full suite 5,354 passed / 0 failed; the corpus differential against the frozen baseline reconciled row-for-row (+27/−9, each class explained in the commit message); 20/20 sampled call edges verified at cited The oracle file is included in the PR deliberately: it's ~150 lines, runs in seconds with no API calls, and turns the omission-defect class — which dominated all four review rounds — into a mechanical check any future language extractor can copy. Happy to drop it from the diff if you'd rather keep the PR minimal. |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds F# extraction for .fs/.fsx/.fsx files behind an optional [fsharp] extra, wiring extract_fsharp into the dispatch, extension families, and detect's code-extension set so modules, namespaces, record/union/class types with cases and members, let-bound functions/values, open imports, and pipeline calls (|>, <|) are captured, with .fs joining the dotnet family so F#→C# references rewire across the seam. Routes .fs files through _looks_like_fsharp_source, a 64 KB comment-aware sniffer that keeps GLSL fragment shaders and Forth off the F# extractor by weighing strong GLSL directives against strong F# declaration line-starts. Runs the .NET import-repoint pass for pure-F# corpora via _DOTNET_IMPORT_EXTS so open edges survive build's dangling-edge prune, while leaving the C#-shaped type-reference resolver gated on .cs sources only.
Worth a look
- Dangling if makes coverage oracle invalid Python —
tests/fsharp_coverage_oracle.py:244· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Truncated Python file with dangling if body causes SyntaxError —
tests/fsharp_coverage_oracle.py:249· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- BOM-prefixed .fs files are rejected by the F# routing heuristic —
graphify/extract.py:5619· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Oracle requires a nonstandard temp directory —
tests/fsharp_coverage_oracle.py:218· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Missing-grammar test is skipped when the grammar is actually missing —
tests/test_fsharp.py:8· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3411 functions depend on the 748 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 524 callers, 43 callees - new:
_rebuild_code()— 113 callers, 50 callees - new:
build_from_json()— 192 callers, 18 callees - new:
detect()— 108 callers, 15 callees - new:
build_merge()— 62 callers, 13 callees - new:
to_obsidian()— 36 callers, 13 callees - new:
save_manifest()— 40 callers, 11 callees - new:
extract_fsharp()— 43 callers, 9 callees - …and 87 more — each is listed as a finding
Verification — 3411 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 3001 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify extract.
The verifier did not have enough to check extract, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_get\_extractor.
The verifier did not have enough to check \_get\_extractor, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
· 6 grounded finding(s) anchored inline below; 89 more finding(s) on lines outside this diff (see the check run).
| return False | ||
|
|
||
|
|
||
| def _get_extractor(path: Path) -> Any | None: |
There was a problem hiding this comment.
_get_extractor()
fans out to 7 callees (efferent coupling); 34 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| _CALLEE_TYPES = frozenset({"long_identifier_or_op", "dot_expression"}) | ||
|
|
||
|
|
||
| def extract_fsharp(path: Path) -> dict: |
There was a problem hiding this comment.
extract_fsharp()
fans out to 9 callees (efferent coupling); 43 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return [], line_of(tn) | ||
| return identifiers_of(name_node), line_of(tn) | ||
|
|
||
| def emit_cases(defn, type_nid: str) -> None: |
There was a problem hiding this comment.
emit_cases()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| add_edge(type_nid, cnid, "contains", line_of(case)) | ||
| register_def(cname, cnid) | ||
|
|
||
| def emit_member(member_defn, type_nid: str) -> str | None: |
There was a problem hiding this comment.
emit_member()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return out | ||
| return [] | ||
|
|
||
| def mint_binding_head(head, container_nid: str) -> str | None: |
There was a problem hiding this comment.
mint_binding_head()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| call_sites.append((caller, callee, qualifier, | ||
| ".".join(parts), line_of(callee_node))) | ||
|
|
||
| def walk(node, container_nid: str, enclosing_value: str) -> None: |
There was a problem hiding this comment.
walk()
fans out to 17 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Three of five advisory findings verified real (two HIGHs refuted with compile evidence — see PR thread): - The coverage oracle used a machine-local temp dir (dir="/data/tmp"); now the system default, so it runs anywhere including upstream CI. - The .fs sniffer strips a leading UTF-8 BOM before line-anchoring — Windows-authored F# commonly leads with one, and a single-marker-line file would otherwise miss its only strong F# marker. Regression test. - test_missing_grammar_is_reported_not_raised moved out of the importorskip'd module: it FAKES the ImportError, so it must run precisely on machines where the grammar is actually absent.
|
Response to the latest automated review (5 findings) — verified each before acting, per this PR's standing practice: Refuted with evidence (the two HIGHs): Accepted and fixed (latest commit):
57 tests + 58/58 oracle green after the fold. |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds F# extraction behind an optional [fsharp] extra, wiring .fs/.fsx through extract_fsharp and registering them across the code-extension, language-family, and edge-family tables so F# joins the dotnet interop family for cross-language reference rewiring. Routes .fs through _looks_like_fsharp_source, an evidence-tiered content sniff that skips comments and gives GLSL directives and F# declaration line-starts strong precedence so shader/Forth .fs files fall through to no extractor (unreadable files default to the F# extractor to surface the real error). Extends the .NET import-repoint pass to run for pure-F# corpora via _DOTNET_IMPORT_EXTS so F# open edges survive build's dangling-edge prune, while keeping the C#-shaped type-reference resolver gated on .cs/.razor/.cshtml.
Worth a look
- run_one drops accumulated failures on normal paths —
tests/fsharp_coverage_oracle.py:250· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- _looks_like_fsharp_source rejects valid F# files with no strong markers in first 64KB —
graphify/extract.py:5669· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Qualified local F# calls resolve by short member name only —
graphify/extractors/fsharp.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Self-edge check raises NameError instead of reporting a problem —
tests/fsharp_coverage_oracle.py:250· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Undefined variable in F# extension test —
tests/test_fsharp.py:513· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3412 functions depend on the 749 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 524 callers, 43 callees - new:
_rebuild_code()— 113 callers, 50 callees - new:
build_from_json()— 192 callers, 18 callees - new:
detect()— 108 callers, 15 callees - new:
build_merge()— 62 callers, 13 callees - new:
to_obsidian()— 36 callers, 13 callees - new:
save_manifest()— 40 callers, 11 callees - new:
extract_fsharp()— 44 callers, 9 callees - …and 87 more — each is listed as a finding
Verification — 3412 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 3002 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify extract.
The verifier did not have enough to check extract, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_get\_extractor.
The verifier did not have enough to check \_get\_extractor, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
· 6 grounded finding(s) anchored inline below; 89 more finding(s) on lines outside this diff (see the check run).
| return False | ||
|
|
||
|
|
||
| def _get_extractor(path: Path) -> Any | None: |
There was a problem hiding this comment.
_get_extractor()
fans out to 7 callees (efferent coupling); 35 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| _CALLEE_TYPES = frozenset({"long_identifier_or_op", "dot_expression"}) | ||
|
|
||
|
|
||
| def extract_fsharp(path: Path) -> dict: |
There was a problem hiding this comment.
extract_fsharp()
fans out to 9 callees (efferent coupling); 44 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return [], line_of(tn) | ||
| return identifiers_of(name_node), line_of(tn) | ||
|
|
||
| def emit_cases(defn, type_nid: str) -> None: |
There was a problem hiding this comment.
emit_cases()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| add_edge(type_nid, cnid, "contains", line_of(case)) | ||
| register_def(cname, cnid) | ||
|
|
||
| def emit_member(member_defn, type_nid: str) -> str | None: |
There was a problem hiding this comment.
emit_member()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return out | ||
| return [] | ||
|
|
||
| def mint_binding_head(head, container_nid: str) -> str | None: |
There was a problem hiding this comment.
mint_binding_head()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| call_sites.append((caller, callee, qualifier, | ||
| ".".join(parts), line_of(callee_node))) | ||
|
|
||
| def walk(node, container_nid: str, enclosing_value: str) -> None: |
There was a problem hiding this comment.
walk()
fans out to 17 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Bot round-6 find (the automated review's first probe-level catch missed by all four panel arms): `B.helper` where module B defines no helper bound EXTRACTED to A's helper — the qualifier gate only checked that the qualifier was SOME local container, never that the callee belonged to that container. register_def now has a per-container sibling index: qualified calls bind EXTRACTED only via the qualifier's own members; a local qualifier that does not own the name yields a full-path INFERRED stub (rewireable, but never a false local edge). A container label claimed twice in one file is ambiguous and binds nothing. Corpus effect: 85 receiver-qualified calls (d.U32, capture.Snapshot, ...) demoted from false EXTRACTED local binds to honest full-path stubs, and container-scoped positives added correct binds (+54 net calls). 58 F# tests + 58/58 oracle + all mutants killed + full suite 5,356/0.
|
Response to the latest automated review (5 findings), each verified before acting: Real — fixed in the latest commit, and credit where due: Qualified local F# calls resolve by short member name only is a genuine defect that four adversarial review arms missed and this review caught first. Reproduced: Refuted with evidence (including the HIGH):
Working as designed: sniffer rejects F# with no strong markers in 64KB — a compilable 58 tests + 58/58 oracle + full suite 5,356/0 after the fold. |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds F# .fs/.fsx extraction behind an optional [fsharp] extra, pulling out modules, namespaces, record/union/class types with cases and members, let-bound functions and values, open imports, and calls including pipeline application. Routes .fs/.fsx into the dotnet interop family across detect/analyze/build so F#→C# references rewire across the seam, and generalizes the .NET import repoint to run for F#-only corpora (previously gated on .cs, which silently dropped F# open edges in the dangling-edge prune) while leaving the C#-shaped type-reference resolver gated on C# sources. Guards .fs with _looks_like_fsharp_source, an evidence-tiered sniff that ignores comment-only lines, treats GLSL directives as strong negative and F# declaration line-starts as strong positive within a 64 KB window, so GLSL fragment shaders and Forth aren't mis-routed to the F# extractor.
Worth a look
- F# signature files are not routed to the F# extractor —
graphify/extract.py:5424· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- F# detection reads entire file despite 64KB limit —
graphify/extract.py:5621· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- F# interface implementations are never emitted —
graphify/extractors/fsharp.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- find_all drops nested matches of the same node type —
graphify/extractors/fsharp.py:211· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Self-edge detection raises NameError —
tests/fsharp_coverage_oracle.py:250· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3415 functions depend on the 752 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 524 callers, 43 callees - new:
_rebuild_code()— 113 callers, 50 callees - new:
build_from_json()— 192 callers, 18 callees - new:
detect()— 108 callers, 15 callees - new:
build_merge()— 62 callers, 13 callees - new:
to_obsidian()— 36 callers, 13 callees - new:
save_manifest()— 40 callers, 11 callees - new:
extract_fsharp()— 45 callers, 9 callees - …and 87 more — each is listed as a finding
Verification — 3415 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 3005 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify extract.
The verifier did not have enough to check extract, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_get\_extractor.
The verifier did not have enough to check \_get\_extractor, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
· 6 grounded finding(s) anchored inline below; 89 more finding(s) on lines outside this diff (see the check run).
| return False | ||
|
|
||
|
|
||
| def _get_extractor(path: Path) -> Any | None: |
There was a problem hiding this comment.
_get_extractor()
fans out to 7 callees (efferent coupling); 35 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| _CALLEE_TYPES = frozenset({"long_identifier_or_op", "dot_expression"}) | ||
|
|
||
|
|
||
| def extract_fsharp(path: Path) -> dict: |
There was a problem hiding this comment.
extract_fsharp()
fans out to 9 callees (efferent coupling); 45 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return [], line_of(tn) | ||
| return identifiers_of(name_node), line_of(tn) | ||
|
|
||
| def emit_cases(defn, type_nid: str) -> None: |
There was a problem hiding this comment.
emit_cases()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| register_def(cname, cnid) | ||
| register_member(type_nid, cname, cnid) | ||
|
|
||
| def emit_member(member_defn, type_nid: str) -> str | None: |
There was a problem hiding this comment.
emit_member()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return out | ||
| return [] | ||
|
|
||
| def mint_binding_head(head, container_nid: str) -> str | None: |
There was a problem hiding this comment.
mint_binding_head()
fans out to 9 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| call_sites.append((caller, callee, qualifier, | ||
| ".".join(parts), line_of(callee_node))) | ||
|
|
||
| def walk(node, container_nid: str, enclosing_value: str) -> None: |
There was a problem hiding this comment.
walk()
fans out to 18 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Bot round-7: one real micro-issue taken (the sniffer read the whole file before slicing to 64KB — now a bounded read); nested-destructuring find_all behavior pinned with a test and an intent comment (the grammar nests inner patterns beside identifier_pattern, never inside one, so stop-at-match is correct — probed live). Interface-implementation emission re-verified on the record shape (edge present); .fsi remains a declared deferral; the oracle NameError report is a repeat of the round-6 refutation (file runs 58/58, exit 0).
|
Response to the latest automated review (5 findings), verified per standing practice: Taken (1): detection reads entire file despite 64KB limit — correct as a micro-issue: Refuted by probe (2): F# interface implementations are never emitted — they are, on every definition shape: probed a record-shaped Repeat of a refuted finding (1): self-edge detection raises NameError at Declared deferral (1): .fsi signature files not routed — deliberate v1 scope from the PR description onward ( 59 tests + 58/58 oracle green after the fold. |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds F# (.fs/.fsx) extraction behind an optional [fsharp] extra, emitting modules, namespaces, record/union/class types with cases and members, let-bound functions/values, open imports, and calls including pipeline application. Routes .fs through _looks_like_fsharp_source, which reads a bounded 64 KB window and uses tiered comment-aware evidence to reject GLSL fragment shaders and Forth while letting BOM-led and expression-heavy F# through. Joins .fs/.fsx to the dotnet interop family across analyze/build/detect so F#→C# references rewire, and widens the import-repoint gate to _DOTNET_IMPORT_EXTS so F#-only corpora keep their open edges instead of losing them to the dangling-edge prune, while the C#-shaped type-reference resolver stays gated on .cs.
Worth a look
- Unexpected indentation makes F# extractor unparsable —
graphify/extractors/fsharp.py· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- F# type extraction returns undefined name —
graphify/extractors/fsharp.py:260· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Trailing multi-line block-comment start (*) matched as strong F# by prefix —
graphify/extract.py:5648· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Self-edge check raises NameError instead of reporting a problem —
tests/fsharp_coverage_oracle.py:250· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Undefined variable in new F# test —
tests/test_fsharp.py:291· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3416 functions depend on the 753 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 524 callers, 43 callees - new:
_rebuild_code()— 113 callers, 50 callees - new:
build_from_json()— 192 callers, 18 callees - new:
detect()— 108 callers, 15 callees - new:
build_merge()— 62 callers, 13 callees - new:
to_obsidian()— 36 callers, 13 callees - new:
save_manifest()— 40 callers, 11 callees - new:
extract_fsharp()— 46 callers, 9 callees - …and 87 more — each is listed as a finding
Verification — 3416 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 3006 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify extract.
The verifier did not have enough to check extract, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_get\_extractor.
The verifier did not have enough to check \_get\_extractor, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
· 6 grounded finding(s) anchored inline below; 89 more finding(s) on lines outside this diff (see the check run).
| return False | ||
|
|
||
|
|
||
| def _get_extractor(path: Path) -> Any | None: |
There was a problem hiding this comment.
_get_extractor()
fans out to 7 callees (efferent coupling); 35 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| _CALLEE_TYPES = frozenset({"long_identifier_or_op", "dot_expression"}) | ||
|
|
||
|
|
||
| def extract_fsharp(path: Path) -> dict: |
There was a problem hiding this comment.
extract_fsharp()
fans out to 9 callees (efferent coupling); 46 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return [], line_of(tn) | ||
| return identifiers_of(name_node), line_of(tn) | ||
|
|
||
| def emit_cases(defn, type_nid: str) -> None: |
There was a problem hiding this comment.
emit_cases()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| register_def(cname, cnid) | ||
| register_member(type_nid, cname, cnid) | ||
|
|
||
| def emit_member(member_defn, type_nid: str) -> str | None: |
There was a problem hiding this comment.
emit_member()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return out | ||
| return [] | ||
|
|
||
| def mint_binding_head(head, container_nid: str) -> str | None: |
There was a problem hiding this comment.
mint_binding_head()
fans out to 9 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| call_sites.append((caller, callee, qualifier, | ||
| ".".join(parts), line_of(callee_node))) | ||
|
|
||
| def walk(node, container_nid: str, enclosing_value: str) -> None: |
There was a problem hiding this comment.
walk()
fans out to 18 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Bot round-8 find, probe-confirmed: a Forth .fs file with a line starting '(*' dispatched to the F# extractor. The marker was for block-comment license headers, but any compilable F# file surfaces a real declaration line within the 64KB window anyway. Regression tests both ways (Forth rejected; block-comment-headed F# still dispatched).
|
Latest automated review (5 findings), verified per standing practice: Real — fixed (1): Refuted, for the record (both HIGHs): extractor unparsable due to indentation and undefined name at :260 — the file compiles ( Repeat (1): the oracle self-edge NameError is now reported a third time unchanged; refuted twice previously with run evidence ( |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds a first-class F# extractor for .fs/.fsx files behind an optional [fsharp] extra, wiring extract_fsharp into the dispatch table, CODE_EXTENSIONS, and the dotnet language family so F#→C# references rewire across the interop seam. Routes .fs through _looks_like_fsharp_source, an evidence-tiered 64KB heuristic that skips comments and defers to strong GLSL directives so fragment shaders and Forth files aren't misclassified as F#. Ungates the dotnet import repoint from C#-only so F# open edges survive build's dangling-edge prune, while keeping the C#-shaped type-reference resolver gated on .cs sources.
Worth a look
- in vec / out vec strong_glsl markers can shadow legitimate F# but ordering makes GLSL win incorrectly —
graphify/extract.py:5680· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- type_name_parts returns undefined name instead of type parts —
graphify/extractors/fsharp.py:260· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Interface implementations are never emitted —
graphify/extractors/fsharp.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Oracle script never executes its checks —
tests/fsharp_coverage_oracle.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- run_one never returns accumulated failures —
tests/fsharp_coverage_oracle.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3418 functions depend on the 755 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 524 callers, 43 callees - new:
_rebuild_code()— 113 callers, 50 callees - new:
build_from_json()— 192 callers, 18 callees - new:
detect()— 108 callers, 15 callees - new:
build_merge()— 62 callers, 13 callees - new:
to_obsidian()— 36 callers, 13 callees - new:
save_manifest()— 40 callers, 11 callees - new:
extract_fsharp()— 46 callers, 9 callees - …and 87 more — each is listed as a finding
Verification — 3418 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 3008 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify extract.
The verifier did not have enough to check extract, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_get\_extractor.
The verifier did not have enough to check \_get\_extractor, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
· 6 grounded finding(s) anchored inline below; 89 more finding(s) on lines outside this diff (see the check run).
| return False | ||
|
|
||
|
|
||
| def _get_extractor(path: Path) -> Any | None: |
There was a problem hiding this comment.
_get_extractor()
fans out to 7 callees (efferent coupling); 37 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| _CALLEE_TYPES = frozenset({"long_identifier_or_op", "dot_expression"}) | ||
|
|
||
|
|
||
| def extract_fsharp(path: Path) -> dict: |
There was a problem hiding this comment.
extract_fsharp()
fans out to 9 callees (efferent coupling); 46 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return [], line_of(tn) | ||
| return identifiers_of(name_node), line_of(tn) | ||
|
|
||
| def emit_cases(defn, type_nid: str) -> None: |
There was a problem hiding this comment.
emit_cases()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| register_def(cname, cnid) | ||
| register_member(type_nid, cname, cnid) | ||
|
|
||
| def emit_member(member_defn, type_nid: str) -> str | None: |
There was a problem hiding this comment.
emit_member()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return out | ||
| return [] | ||
|
|
||
| def mint_binding_head(head, container_nid: str) -> str | None: |
There was a problem hiding this comment.
mint_binding_head()
fans out to 9 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| call_sites.append((caller, callee, qualifier, | ||
| ".".join(parts), line_of(callee_node))) | ||
|
|
||
| def walk(node, container_nid: str, enclosing_value: str) -> None: |
There was a problem hiding this comment.
walk()
fans out to 18 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Bot round-9 find, probe-confirmed: 'uniform ' as STRONG GLSL evidence dropped a real F# file whose continuation line calls a function named uniform. Strong tier now holds only shapes impossible as F# line-starts (directives, typed io decls like 'in vec3', void main, gl_); uniform/ varying/precision join the weak tier, which never overrides a strong F# declaration. Regression tests both ways; all prior shader cases still reject; 0 of 100 corpus .fs files affected either way.
|
Latest automated review (5 findings), verified per standing practice: Real — fixed (1): GLSL word-markers can shadow legitimate F# — probe-confirmed for the word class: Repeats of refuted findings (4): type_name_parts undefined name (2nd repeat — file compiles, 63 tests pass), interface implementations never emitted (2nd repeat — probed on class, record, and generic shapes; edges present and test-pinned), and both oracle claims (never executes checks / run_one never returns failures, 4th round of this family — |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds F# .fs/.fsx extraction behind an optional [fsharp] extra, capturing modules, namespaces, record/union/class types with cases and members, let-bound functions/values, open imports, and calls including pipeline application, and folds .fs into the dotnet family so F#→C# references rewire across the seam. Routes .fs through _looks_like_fsharp_source, which reads a bounded 64KB window and uses tiered line-start evidence (strong GLSL directives, strong F# declarations, weak type keywords) to keep GLSL fragment shaders and Forth from being parsed as F#, skipping extraction when the file doesn't look like F#. Registers .fs/.fsx across detection, dispatch, and edge-family tables so the C#-namespace import repointing also runs for pure-F# corpora.
Worth a look
- type_name_parts returns the identifiers_of function instead of parsed type name parts —
graphify/extractors/fsharp.py:257· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Valid F# identifiers starting with layout are treated as strong GLSL —
graphify/extract.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Self-edge check raises NameError instead of reporting a problem —
tests/fsharp_coverage_oracle.py:249· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3420 functions depend on the 757 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 524 callers, 43 callees - new:
_rebuild_code()— 113 callers, 50 callees - new:
build_from_json()— 192 callers, 18 callees - new:
detect()— 108 callers, 15 callees - new:
build_merge()— 62 callers, 13 callees - new:
to_obsidian()— 36 callers, 13 callees - new:
save_manifest()— 40 callers, 11 callees - new:
extract_fsharp()— 46 callers, 9 callees - …and 87 more — each is listed as a finding
Verification — 3420 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 3010 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify extract.
The verifier did not have enough to check extract, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_get\_extractor.
The verifier did not have enough to check \_get\_extractor, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
· 6 grounded finding(s) anchored inline below; 89 more finding(s) on lines outside this diff (see the check run).
| return False | ||
|
|
||
|
|
||
| def _get_extractor(path: Path) -> Any | None: |
There was a problem hiding this comment.
_get_extractor()
fans out to 7 callees (efferent coupling); 39 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| _CALLEE_TYPES = frozenset({"long_identifier_or_op", "dot_expression"}) | ||
|
|
||
|
|
||
| def extract_fsharp(path: Path) -> dict: |
There was a problem hiding this comment.
extract_fsharp()
fans out to 9 callees (efferent coupling); 46 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return [], line_of(tn) | ||
| return identifiers_of(name_node), line_of(tn) | ||
|
|
||
| def emit_cases(defn, type_nid: str) -> None: |
There was a problem hiding this comment.
emit_cases()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| register_def(cname, cnid) | ||
| register_member(type_nid, cname, cnid) | ||
|
|
||
| def emit_member(member_defn, type_nid: str) -> str | None: |
There was a problem hiding this comment.
emit_member()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return out | ||
| return [] | ||
|
|
||
| def mint_binding_head(head, container_nid: str) -> str | None: |
There was a problem hiding this comment.
mint_binding_head()
fans out to 9 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| call_sites.append((caller, callee, qualifier, | ||
| ".".join(parts), line_of(callee_node))) | ||
|
|
||
| def walk(node, container_nid: str, enclosing_value: str) -> None: |
There was a problem hiding this comment.
walk()
fans out to 18 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
…dentifier Bot round-10, probe-confirmed: F# calling a function named layout on a continuation line dropped the whole file. Applies the round-9 principle uniformly: only shapes impossible as F# line-starts stay strong. Shader cases hold via 'void main'/weak accumulation; 0 corpus files affected.
|
Latest automated review (3 findings): Real — fixed (1): Repeats (2): type_name_parts returns the function — third phrasing of the |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds F# .fs/.fsx extraction behind an optional [fsharp] extra, via extract_fsharp, capturing modules, namespaces, record/union/class types with cases and members, let-bound functions and values, open imports, and pipeline-aware calls (|>, <|). Routes .fs/.fsx into the dotnet language family across analyze, build, detect, and extract so F#→C# references rewire across the interop seam, and runs the imports repoint pass on pure-F# corpora too. Guards .fs dispatch with _looks_like_fsharp_source, which reads a bounded 64 KB head (BOM-stripped, comment-aware) and uses tiered evidence — strong GLSL directives win, strong F# declarations override weak type-keyword lines — to keep GLSL fragment shaders and Forth files off the F# extractor.
Worth a look
- type_name_parts returns undefined name instead of parsed type name —
graphify/extractors/fsharp.py:264· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- weak_glsl marker 'int ' / 'bool ' collides with valid F# expression lines and rejects real F# files —
graphify/extract.py:5688· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- F# type-name helper can raise NameError instead of returning extraction data —
graphify/extractors/fsharp.py:260· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Self-edge check raises NameError instead of recording a problem —
tests/fsharp_coverage_oracle.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Undefined variable in F# type extension test —
tests/test_fsharp.py:286· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3422 functions depend on the 759 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 524 callers, 43 callees - new:
_rebuild_code()— 113 callers, 50 callees - new:
build_from_json()— 192 callers, 18 callees - new:
detect()— 108 callers, 15 callees - new:
build_merge()— 62 callers, 13 callees - new:
to_obsidian()— 36 callers, 13 callees - new:
save_manifest()— 40 callers, 11 callees - new:
extract_fsharp()— 46 callers, 9 callees - …and 87 more — each is listed as a finding
Verification — 3422 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 3012 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify extract.
The verifier did not have enough to check extract, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_get\_extractor.
The verifier did not have enough to check \_get\_extractor, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
· 6 grounded finding(s) anchored inline below; 89 more finding(s) on lines outside this diff (see the check run).
| return False | ||
|
|
||
|
|
||
| def _get_extractor(path: Path) -> Any | None: |
There was a problem hiding this comment.
_get_extractor()
fans out to 7 callees (efferent coupling); 41 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| _CALLEE_TYPES = frozenset({"long_identifier_or_op", "dot_expression"}) | ||
|
|
||
|
|
||
| def extract_fsharp(path: Path) -> dict: |
There was a problem hiding this comment.
extract_fsharp()
fans out to 9 callees (efferent coupling); 46 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return [], line_of(tn) | ||
| return identifiers_of(name_node), line_of(tn) | ||
|
|
||
| def emit_cases(defn, type_nid: str) -> None: |
There was a problem hiding this comment.
emit_cases()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| register_def(cname, cnid) | ||
| register_member(type_nid, cname, cnid) | ||
|
|
||
| def emit_member(member_defn, type_nid: str) -> str | None: |
There was a problem hiding this comment.
emit_member()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return out | ||
| return [] | ||
|
|
||
| def mint_binding_head(head, container_nid: str) -> str | None: |
There was a problem hiding this comment.
mint_binding_head()
fans out to 9 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| call_sites.append((caller, callee, qualifier, | ||
| ".".join(parts), line_of(callee_node))) | ||
|
|
||
| def walk(node, container_nid: str, enclosing_value: str) -> None: |
There was a problem hiding this comment.
walk()
fans out to 18 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
…s directives only Closes the marker class that bot rounds 8-10 had been walking one word per round ((* / uniform / layout): a proactive audit probed every remaining strong marker against tree-sitter-fsharp itself for line-start validity. Result: gl_ (interop identifiers: `gl_makeCurrent ctx`), `in vecN` (verbose `let x = 1.0` / `in vec2 x x`), `out vecN` (`out` is a plain identifier), and `void main` (`let void = 1` parses -- void is not a lexer-level F# keyword) are ALL grammar-valid F# line-starts, so any of them as strong evidence rejects a real F# file whole. Only #version/#extension are grammar-invalid at an F# line start; the strong tier is now exactly those two directives, and every word-shaped marker lives in the weak tier, which never overrides a strong F# declaration. Also re-anchors test_glsl_guard_is_load_bearing_for_marker_collisions on '#version' -- its previous shader would now reject via the weak/default path, so the delete-strong-tier mutant needed a new carrier. Gates: 70 F# tests (5 new: one acceptance per demoted family, killing its re-promotion mutant, plus a headerless gl_ shader rejection), oracle 58/58, full suite green, corpus reclassification 99/99 .fs files unchanged (0 flips -- no extractor-output change), all 5 targeted mutants killed (4 re-promotions + strong-tier deletion).
|
Rounds 8–10 each surfaced one word-shaped sniff marker that was strong enough to drop a real F# file ( Every word-shaped marker fails the audit — each is a valid F# line-start:
Only On this round's five advisory findings: the |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds F# extraction for .fs/.fsx files behind an optional [fsharp] extra, routing them through extract_fsharp and wiring the extension into CODE_EXTENSIONS, the dispatch table, and the per-extension extra map. Places .fs/.fsx in the dotnet interop family across analyze, build, and extract so F#→C# references rewire across the seam. Guards the .fs route with _looks_like_fsharp_source, a tiered 64 KB sniff that skips comment lines, treats #version/#extension as decisive GLSL and F# declaration line-starts as decisive F#, and rejects shader-shaped files when no strong F# evidence appears — defaulting to reject on ambiguity so GLSL/Forth .fs files don't get parsed as F#.
Worth a look
- F# extractor appears to introduce an unexpected indent —
graphify/extractors/fsharp.py· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- type_name_parts returns undefined name —
graphify/extractors/fsharp.py:262· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- F# interface implementations are not emitted despite contract —
graphify/extractors/fsharp.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Self-edge check raises NameError instead of recording a problem —
tests/fsharp_coverage_oracle.py:250· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Undefined variable in F# test assertion —
tests/test_fsharp.py:288· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3427 functions depend on the 764 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 524 callers, 43 callees - new:
_rebuild_code()— 113 callers, 50 callees - new:
build_from_json()— 192 callers, 18 callees - new:
detect()— 108 callers, 15 callees - new:
build_merge()— 62 callers, 13 callees - new:
to_obsidian()— 36 callers, 13 callees - new:
save_manifest()— 40 callers, 11 callees - new:
extract_fsharp()— 46 callers, 9 callees - …and 87 more — each is listed as a finding
Verification — 3427 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 3017 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify extract.
The verifier did not have enough to check extract, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_get\_extractor.
The verifier did not have enough to check \_get\_extractor, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
· 6 grounded finding(s) anchored inline below; 89 more finding(s) on lines outside this diff (see the check run).
| return False | ||
|
|
||
|
|
||
| def _get_extractor(path: Path) -> Any | None: |
There was a problem hiding this comment.
_get_extractor()
fans out to 7 callees (efferent coupling); 46 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| _CALLEE_TYPES = frozenset({"long_identifier_or_op", "dot_expression"}) | ||
|
|
||
|
|
||
| def extract_fsharp(path: Path) -> dict: |
There was a problem hiding this comment.
extract_fsharp()
fans out to 9 callees (efferent coupling); 46 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return [], line_of(tn) | ||
| return identifiers_of(name_node), line_of(tn) | ||
|
|
||
| def emit_cases(defn, type_nid: str) -> None: |
There was a problem hiding this comment.
emit_cases()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| register_def(cname, cnid) | ||
| register_member(type_nid, cname, cnid) | ||
|
|
||
| def emit_member(member_defn, type_nid: str) -> str | None: |
There was a problem hiding this comment.
emit_member()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return out | ||
| return [] | ||
|
|
||
| def mint_binding_head(head, container_nid: str) -> str | None: |
There was a problem hiding this comment.
mint_binding_head()
fans out to 9 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| call_sites.append((caller, callee, qualifier, | ||
| ".".join(parts), line_of(callee_node))) | ||
|
|
||
| def walk(node, container_nid: str, enclosing_value: str) -> None: |
There was a problem hiding this comment.
walk()
fans out to 18 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
|
For the record on the round-12 HIGH 'F# extractor appears to introduce an unexpected indent': probed at 9b5ca4b — py_compile and tokenize both pass clean on graphify/extractors/fsharp.py (no IndentationError/TabError, zero tab characters), and the full suite (5,367 tests) imports and executes the module. A real unexpected indent would make the module unimportable. The other four findings this round repeat items already refuted above (type_name_parts, the oracle self-edge NameError, interface-impls, and the tests/test_fsharp.py 'undefined variable' — still a line inside an F# string literal; the anchor drifted :286 to :288). |
What
F# support:
.fs/.fsxfiles are extracted via ionide's tree-sitter-fsharp (0.3.11, prebuilt abi3 wheels for every platform), as an optional[fsharp]extra — matching the recent precedent of[ocaml]/[commonlisp]/[robot].Extracted: modules, namespaces, record/union/interface/enum/class types, union cases, class members, let-bound functions and values,
openimports, and calls — including pipeline application (x |> f,f <| x, and the 2/3-ary variants), which is where most F# call edges live..fs/.fsxjoin thedotnetinterop family in all three family maps (extract._LANG_FAMILY_BY_EXT,analyze._LANG_FAMILY,build._EDGE_LANG_FAMILY), so an F# reference to a C# type rewires onto the real definition instead of dangling.How
graphify/extractors/fsharp.pyfollowsextractors/ocaml.pyclosely (F# is ML-family): the same sourceless ref-stub discipline (#1402), the same local-definition table with ambiguity tracking, the same two-pass call resolution, and the same qualified-call gating (a callM.fwhereMis not defined locally stays a distinct stub keyed by its full path, soList.mapcan never absorb a localmap).F#-specific handling, grounded in live AST probes of the grammar rather than
tags.scmalone:type_definition > *_type_defn > type_name,function_declaration_left > identifier,value_declaration_left > identifier_pattern > long_identifier_or_op, andmethod_or_prop_defn > property_or_ident(last identifier —this.Runcarries two).infix_expressions: the callee is the right operand for|>-family, the left for<|-family; both feed the same call-site table asapplication_expression.Verification
Beyond the unit tests (
tests/test_fsharp.py), two things this PR did that I'd flag for review:One named test per registration point, mutation-checked.
tests/test_fsharp_registration.pycovers each wiring point (dispatch ×2,CODE_EXTENSIONS, watch, extra-hint, and all three family maps) individually. I then removed each registration in turn and confirmed a test fails. The first run caught an untested.fsxdispatch entry — the exact silent-skip failure mode that made F# invisible in the first place (a file matching no category produces no error).Graded on a real mixed Rust/C#/F# repo (a .NET instrumentation platform, ~470k LOC, 100 F# files including a full F# sidecar service): 2,361 sourced nodes, 10,238 call edges; 20 randomly sampled call edges all verified by hand at their cited
file:line. Cross-language: 192 C#↔F# edges where the release build produces zero.The grade also caught a bug the fixture tests missed, now fixed with a regression test:
let subscribe (a: A) (b: B) : IDisposable = ...parses as avalue_declaration_leftwhose last identifier is the return-type annotation — the first cut minted a sourcedIDisposablenode, and thedotnetfamily rewire then bound every BCLimplements IDisposablestub in the corpus onto it (82 false cross-language edges). The name is now taken from the firstlong_identifier_or_opunderidentifier_pattern.Known limits (deliberate v1 scope)
.fsisignature files not wired (language_signature()exists in the grammar; follow-up).async { }/task { }bodies) and#r/#loadscript directives are not specially handled.{ new IDisposable with ... }) contribute their member bodies' calls but no interface edge.Checks
uv run pytest tests/ -q: 5,315 passed; the only 2 failures (test_labeling.py::test_label_communities_batches_when_over_batch_size,test_watch.py::test_watch_handler_honors_graphifyignore) reproduce on cleanv8without this change.uv lockupdated;uv sync --all-extras --frozenclean.