Skip to content

fix(indexer): disambiguate node identity so colliding symbols stop merging - #1630

Merged
Shashankss1205 merged 15 commits into
CodeGraphContext:mainfrom
Falehaqazi:fix/1393-node-identity-collision
Aug 18, 2026
Merged

Shashankss1205 merged 15 commits into
CodeGraphContext:mainfrom
Falehaqazi:fix/1393-node-identity-collision

Conversation

@Falehaqazi

Copy link
Copy Markdown
Contributor

The bug

writer.py merged every code entity on (name, path, line_number). That triple is not unique, so two distinct symbols in one file sharing a name and a line collapsed onto a single node, and the following SET n += row overwrote the first one's args, class_context, end_line and cyclomatic_complexity with the last one's.

Reproduction

I re-ran the shipped CSS parser's tree-sitter query over this repo's CSS files:

css files scanned:      5
total parsed selectors: 279
distinct merge keys:    269
lost to collision:      10   (9 colliding keys)

Concrete cases, both in files already in the repo:

file line symbol count
tests/fixtures/sample_projects/sample_project_misc/tables.css 44 tfoot 2
docs/docs/stylesheets/redwood.css 558 code 3
docs/docs/stylesheets/redwood.css 493 tbody, td, tr 2 each

tables.css:44 is tfoot th, tfoot td { } — one grouped rule emitting tfoot twice on one line.

Every collision had distinct start columns, which is worth noting for a future refinement (see "Alternatives" below).

Why not end_line or class_context

The issue suggests end_line, class_context, or a per-file ordinal. Only the ordinal covers both reported sources:

  • end_line — a grouped CSS rule's selectors share a start and end line, so this does not separate them.
  • class_context — the CSS parser (tools/languages/css.py:83) emits neither end_line nor class_context at all, so this is null for every CSS record.

The per-file ordinal works for both CSS and minified JS, and needs no parser changes.

The part the issue does not mention

schema.py carried Neo4j IS UNIQUE constraints on the same three properties for Function, Class, Trait, Interface, Macro, Variable, Struct, Enum, Union, Record and Property. The database was preventing the fix — a writer-only patch throws a constraint violation on Neo4j the moment it tries to create the second colliding node.

The audit in #1393 ran on FalkorDB, where CREATE CONSTRAINT is deliberately skipped (per the comment about the EnforceUniqueEntity null-pointer crash). That is why the bug presented as silent merging rather than an error, and why a fix that looks correct on FalkorDB would break Neo4j.

Changes

persistence/writer.py

  • New pure helper _assign_occurrence_indices() returning a per-item ordinal plus a collision report.
  • occurrence_index added to the merge and match keys.
  • Collisions logged via warning_logger, which also covers the issue's "at minimum, detect and log" fallback.
  • The ordinal is threaded into the parameter dedupe key and the HAS_PARAMETER match, so two colliding functions that share an argument name each keep their own parameters.
  • {"Module", "DbTable", "ExternalClass"} hoisted to _NAME_ONLY_MERGE_LABELS; those are global one-node-per-name labels and keep their existing identity.

schema.py

  • The eleven legacy constraints are dropped and recreated as <label>_identity with the four-property key. Renaming keeps the DROP a no-op on later startups — recreating a constraint on every boot would rebuild the index on a large graph.
  • FalkorDB's supporting composite indexes include occurrence_index.
  • Annotation is untouched: it is not in item_mappings, so its nodes never receive an occurrence_index.

schema_contract.pyFUNCTION_MERGE_KEYS / CLASS_MERGE_KEYS updated, with the existing test adjusted.

Why this is safe

  • occurrence_index is 0 unless two symbols in the same file actually collide, so node identity is byte-identical for the overwhelming majority of symbols.
  • writer.py is the only place these nodes are created — every other reference in src/ is a read-path MATCH.
  • Read paths that match on (name, path, line_number) are unaffected for non-colliding symbols. For the genuinely ambiguous keys they now return both symbols instead of one clobbered node, which is the correct answer.
  • Ordinals cannot drift into stale nodes: update_file_in_graph calls delete_file_from_graph before re-adding.
  • Parse order is deterministic, so re-indexing an unchanged file reproduces the same ordinals.

Verification

After the fix, the same CSS corpus:

total parsed selectors: 279
distinct merge keys:    279
lost:                   0
collisions logged:      9

Tests: tests/unit/tools/test_issue_1393_node_identity.py, 16 tests covering the CSS grouped-selector case, the minified-JS case, three-way collisions, determinism, missing name/line_number, and a model of MERGE + SET n += row showing the old key losing a symbol and the new key preserving both with their own properties.

Full unit suite: 1241 passed, 19 skipped, 0 failed.

Alternatives considered

Start column as the disambiguator. Every collision observed had a distinct column, so column would be a stable, semantically meaningful key rather than an ordinal, and would let read-path matches address a specific symbol. It would require adding column output to every language extractor, so it is out of scope here — but occurrence_index can be swapped for it later without another identity migration if that is the direction you prefer.

Deduplicating at parse time. Not viable: these are genuinely distinct symbols, not duplicate records.

Notes for review

  • Two pre-existing ruff findings in writer.py (F401 sanitize_props, F841 batch_size) are untouched — they are outside this change and not in the CI lint file list.
  • Neo4j applies a property-uniqueness constraint only to nodes that have all the named properties, so any node created without occurrence_index simply falls outside the constraint rather than erroring.

Fixes #1393

@vercel

vercel Bot commented Aug 14, 2026

Copy link
Copy Markdown

@Falehaqazi is attempting to deploy a commit to the shashankss1205's projects Team on Vercel.

A member of the Team first needs to authorize it.

@Falehaqazi

Copy link
Copy Markdown
Contributor Author

Small correction: there were twelve UNIQUE constraints on (name, path, line_number), not eleven. I migrated eleven and deliberately left Annotation — it isn't in item_mappings, so its nodes never receive an occurrence_index and its existing three-property constraint remains correct.

C0deRatoR and others added 14 commits August 18, 2026 22:53
…ive (CodeGraphContext#1641)

* fix(cli/mcp): refuse switch_context while indexing jobs are still active

* fix(php): extract promoted and variadic parameters (CodeGraphContext#1643)

* fix(cli/mcp): stop path→repo_path alias from breaking calculate_cyclomatic_complexity (CodeGraphContext#1638)

---------

Co-authored-by: Yash <C0deRator@proton.me>
Co-authored-by: Shashank Shekhar Singh <Shashankshekharsingh1205@gmail.com>
…GraphContext#1635)

* ci: add cross-OS E2E tests and gracefully skip missing backends

* fix(php): extract promoted and variadic parameters (CodeGraphContext#1643)

* fix(cli/mcp): stop path→repo_path alias from breaking calculate_cyclomatic_complexity (CodeGraphContext#1638)

* fix(cli/mcp): refuse switch_context while indexing jobs are still active (CodeGraphContext#1641)

* fix(cli/mcp): refuse switch_context while indexing jobs are still active

* fix(php): extract promoted and variadic parameters (CodeGraphContext#1643)

* fix(cli/mcp): stop path→repo_path alias from breaking calculate_cyclomatic_complexity (CodeGraphContext#1638)

---------

Co-authored-by: Yash <C0deRator@proton.me>
Co-authored-by: Shashank Shekhar Singh <Shashankshekharsingh1205@gmail.com>

* fix(graph): re-absolutize repo-scoped bundle paths on import so a second bundle is not treated as a duplicate (CodeGraphContext#1627)

* fix pkg_map: falkordb import name is 'falkordb', not 'falkordblite'

---------

Co-authored-by: Yash <C0deRator@proton.me>
Co-authored-by: Ashmeet Singh Sandhu <sandhuashmeet40@gmail.com>
Co-authored-by: Shashank Shekhar Singh <Shashankshekharsingh1205@gmail.com>
…odeGraphContext#1633) (CodeGraphContext#1634)

Every repo_path-scoped query in CodeFinder built its Cypher filter as a raw
`node.path STARTS WITH $repo_path` string comparison with no normalization.
Node paths are stored as the absolute filesystem path used at indexing time,
so passing anything else — the repo name from list_indexed_repositories(),
a relative path, a trailing slash — silently matched zero nodes, with no
error surfaced to the caller.

Adds _normalize_repo_path_filter(), which resolves a non-absolute repo_path
against the indexed repository list (by name, basename, or cwd-relative
path) before it reaches any STARTS WITH filter. Wired into every method on
CodeFinder that accepts repo_path. Also replaces the unrelated but similarly
broken Path(repo_path).resolve() in audit_kotlin_call_ambiguity, which
resolved against the server process cwd instead of the indexed root.

Verified against a local KuzuDB index containing both a Python and a Java
repo: short repo names now resolve to the correct absolute root and return
the expected matches, while cross-repo scoping still correctly excludes the
other repo's results.

Fixes CodeGraphContext#1633.

Co-authored-by: paul.mathieson <paul.mathieson@modulrfinance.com>
…e calls (Fixes CodeGraphContext#1573) (CodeGraphContext#1636)

* Fixes CodeGraphContext#1542: Parameterize relationship query limits, add truncation flags & CLI support

* fix(resolution): don't clobber resolved_called_name for attribute-qualified calls in resolve_function_call()

import m; m.fn() and import m as t; t.fn() produced zero CALLS/HEURISTIC_CALLS edges because the import-alias fallback in resolve_function_call() treated the receiver name (lookup_name == base_obj) as if it were the callee's own name, overwriting resolved_called_name with the module/receiver name. The later writer.py MATCH (by called_name) then found no matching Function node and silently dropped the edge. Guard the overwrite so it only applies to genuine direct-call import aliases (lookup_name == called_name), fixing Python module.fn()/module_alias.fn() call resolution without touching any other resolution tier.

Fixes CodeGraphContext#1573

* fix(resolution): allow import-alias module path lookup for attribute calls without overwriting called_name
…xt#1600, CodeGraphContext#1601, CodeGraphContext#1606, CodeGraphContext#1607, CodeGraphContext#1608) (CodeGraphContext#1632)

* test(macos): resolve the tmp path in the Kotlin parser fixture (CodeGraphContext#1608)

Three tests in TestKotlinFunctionCallResolution failed on a clean macOS
checkout: tempfile hands back /var/folders/... while /var is a symlink to
/private/var, and the call-resolution layer stores the fully resolved
path. Any assertion comparing an edge's called_file_path against
Path(data["path"]).as_posix() therefore compared /private/var/... to
/var/... and failed.

Fixed in _write_and_parse rather than in the assertions. There are 38
such comparison sites in this file; only 3 happened to be reachable in a
shape that tripped the mismatch, so patching those 3 would have left the
other 35 latent for the next test that resolves a path. Resolving once,
before parsing, makes data["path"] canonical everywhere and removes the
need for each call site to remember .resolve().

Not a regression -- it reproduces on a clean tree -- but it made a fresh
macOS checkout look broken and could mask real failures.

tests/unit: 1225 passed, 19 skipped, 0 failed (was 3 failed, 1222 passed).

The same helper shape exists in test_java_parser.py and
test_java_package_qualified_names.py. Those are green, so they are left
alone rather than changed speculatively.

Closes CodeGraphContext#1608

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(writer): land DECORATED_BY edges for decorated classes (CodeGraphContext#1601)

DECORATED_BY is declared as a REL TABLE GROUP with two pairs --
`FROM Function TO Function, FROM Class TO Function` -- and
build_decorated_by_links emits rows for decorated classes correctly. The
writer hardcoded `:Function` on the decorated endpoint, so every class row
matched nothing and was dropped with no error and no log.

The silent drop had two layers, and fixing only the first would have
looked correct while changing nothing:

1. The hardcoded label. Now iterates the declared source labels
   ("Function", "Class") and stops at the first that matches real
   endpoints, exactly as write_binds_links does -- a row carries
   name+path+line but no label, so a same-named node under the other
   label would otherwise pick up a second, spurious edge.

2. The `context` predicate. `Class` has no `context` column, so
   `WHERE ... decorated.context = $decorated_context` raises "Binder
   exception: Cannot find property context for decorated" -- which
   _is_binder_exception catches and swallows. Parameterising the label
   alone still dropped every class row, one layer further down. The
   predicate is now emitted only for the Function label. That is exact,
   not approximate: build_decorated_by_links only ever sets
   decorated_context from a function's class_context and leaves it "" for
   classes, so the predicate was a no-op there regardless.

Effect is not Kotlin-specific -- it is every language that records
class-level decorators. The TypeScript golden gains 4 edges that were
being dropped:

    Class:User        -[DECORATED_BY]-> Function:Entity
    Class:User        -[DECORATED_BY]-> Function:Injectable
    Class:User        -[DECORATED_BY]-> Function:Serializable
    Class:UserService -[DECORATED_BY]-> Function:Component

Only the TypeScript golden is refreshed, and its normalized delta is
exactly those 4 additions: 0 edges removed, 0 node changes. Verified by
diffing through the golden test's own load_and_normalize rather than by
raw file diff -- the raw .jsonl files churn between regenerations
(internal node offsets, and CALLS/HEURISTIC_CALLS reclassification, which
normalization discards by design), so a raw diff would have buried the
signal. Confirmed that regenerating on unmodified main rewrites 62 golden
files while all 21 golden tests still pass, which is why the other
goldens are deliberately left untouched rather than bulk-regenerated.

Test drives the real chain -- parser, builder, writer, query -- because
the defect is precisely that correctly-built rows never land. It uses
Kotlin rather than Python: Python's parser does not populate `decorators`
on classes at all (`@my_decorator class C` yields `[]`), a separate
parser gap that is not this bug and is left unfiled here.

tests/unit: 1228 passed, 19 skipped.
tests/integration: 45 passed.

Closes CodeGraphContext#1601

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(tools): stop find_dead_code capping itself at 50 results (CodeGraphContext#1606)

The query ended with a hardcoded `LIMIT 50` applied *after* the decorator
filter, so excluded rows were backfilled by the next ones in path order
and the returned count was 50 either way. On the reporter's Android
codebase that presented as `exclude_decorated_with` doing nothing while it
was in fact removing 6,086 of 7,141 false positives.

Three consequences, all fixed here:

- The total was unobtainable at any call site. find_dead_code now returns
  `total_count` alongside the page, unconditionally -- a count that
  appears only when truncated is nearly as unusable as no count.
- The handler's pagination was starved. analysis_handlers already read
  TOOL_RESULT_LIMITS and set truncated/result_limit, but never saw more
  than the 50 rows the query returned, so configuring a higher limit
  changed nothing. It now pages a real result set.
- `limit` is a parameter rather than a constant, defaulting to None (all
  rows) at the finder and to the configured tool limit at each caller.

The CLI is updated in the same commit rather than left to follow. Its
table had no page size of its own -- the query's `LIMIT 50` was acting as
one by accident -- so unbounding the finder alone would have printed
several thousand table rows on a real codebase. It now requests the
configured limit explicitly and prints the true total, which is the number
the issue was actually asking for:

    Total: 7141 function(s); showing the first 50 by path

Not done deliberately: the issue's third suggestion, ordering so the
result samples rather than clusters. Ordering is still path then line, so
a limited result is a path-ordered prefix concentrated in the first few
files. That is a product decision about what a truncated sample should
mean rather than a defect, and it is now documented on the method instead
of being silent.

Tests drive the real parser -> writer -> query chain with 70 dead
functions, 30 of them annotated -- above the old cap, and split so the
filtered count (40) coincides with neither 50 nor the unfiltered total,
so a still-capped result cannot pass by accident.

tests/unit: 1233 passed, 19 skipped.
tests/integration: 45 passed.

Closes CodeGraphContext#1606

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(parsers/kotlin): index single-line objects (CodeGraphContext#1600)

The tree-sitter Kotlin grammar misparses `object A { fun x() = 1 }`. It
produces

    infix_expression
      object_literal ("object")
      simple_identifier ("A")
      lambda_literal { statements { function_declaration } }

rather than an `object_declaration`, so the pattern the classes query
looks for is never produced and the singleton is silently missing from
the graph. `object A { }` and the multi-line form parse correctly; only
the one-line-with-a-body shape is affected, and it is a common Kotlin
idiom.

Fixed in three places, because capturing the node alone would have looked
complete while leaving members unattributed:

- The classes query gains an arm for the misparse shape.
- _parse_classes records it under "objects"/Object; without that it would
  fall through to the class_declaration branch and be stored as a Class.
- _get_parent_context and _get_enclosing_class_context recognise it as an
  enclosing scope. This is the half that is easy to miss: the members of
  a misparsed object have no object_declaration ancestor, so `fun x`
  landed at top level with context None while the identical multi-line
  object gives its members context "A".

`object_literal` as the left operand is the discriminator, and it is what
makes matching an `infix_expression` safe: object_literal can only come
from the `object` keyword, so an ordinary infix call with a trailing
lambda (`someValue apply { ... }`) is not captured. There is a boundary
test for exactly that, plus a regression test that the multi-line and
empty forms still produce one object each rather than being duplicated by
the new arm.

Also checked and deliberately not changed: the single-line companion form
`companion object { fun z() = 3 }` parses as a real companion_object and
was never affected. It is pinned by a test so a future change to the new
arm cannot start double-counting it.

tests/unit: 1238 passed, 19 skipped.
tests/integration: 45 passed, no golden changes.

Closes CodeGraphContext#1600

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(writer): store empty list properties as [] rather than [""] (CodeGraphContext#1607)

A function with no decorators persisted `decorators == [""]`, and the same
applied to `args`, `modifiers` and every other list-valued property in
every language. It did not corrupt query results -- `"" CONTAINS
'Preview'` is false, so find_dead_code still retained un-annotated
functions correctly -- but `[""]` is not what any caller means by "none",
and it forced every consumer to special-case a one-element list holding
the empty string.

The issue asked whether the `else [""]` branch was working around a
backend that rejects empty lists, in which case the fix would belong at
the read boundary instead. It is not. Kùzu 0.11.3 accepts `[]` for a
STRING[] column in every shape this writer uses: inside `UNWIND $rows AS
row`, as a single-row parameter, and when *every* row in the batch is
empty so there is no sibling row to infer an element type from. The
dominant-type inference just above is unaffected too -- it only skips
None, and [] is not None, so an always-empty key still resolves to
"list". Both cases have tests; the all-empty batch is the one that would
plausibly have motivated a sentinel.

Golden refresh, 18 of 21 projects. The delta was measured through the
golden test's own load_and_normalize rather than by raw file diff, and it
is exactly 641 node properties changing [""] -> [], with 0 nodes added or
removed and 0 edges added or removed:

    sample_project             80    sample_project_javascript   40
    sample_project_c            3    sample_project_kotlin      110
    sample_project_cpp         26    sample_project_lua           4
    sample_project_csharp      17    sample_project_perl         11
    sample_project_dart        19    sample_project_ruby         12
    sample_project_elixir       3    sample_project_rust         77
    sample_project_go         104    sample_project_scala         4
    sample_project_haskell      3    sample_project_swift        22
    sample_project_java        14    sample_project_typescript   92

The three goldens with no checked change (elisp, misc, php) were reverted
rather than left churning, and they are exactly the three that passed the
integration suite before the refresh -- which cross-checks the
measurement.

Measuring through the normalizer rather than by raw diff is necessary
here, not fastidious: regenerating goldens on unmodified main rewrites 62
files while all 21 golden tests still pass, because the raw .jsonl
carries internal node offsets and load_and_normalize discards
CALLS/HEURISTIC_CALLS edges by design. The property comparison is
intersection-based, matching the check's own
`common_keys = set(exp_node).intersection(act_node)`; the committed
goldens are additionally missing properties the current code emits
(language, is_dependency, visibility), which the check skips and which
this commit deliberately does not touch.

tests/unit: 1240 passed, 19 skipped.
tests/integration: 45 passed.

Closes CodeGraphContext#1607

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(php): extract promoted and variadic parameters (CodeGraphContext#1643)

* fix(cli/mcp): stop path→repo_path alias from breaking calculate_cyclomatic_complexity (CodeGraphContext#1638)

* fix(cli/mcp): refuse switch_context while indexing jobs are still active (CodeGraphContext#1641)

* fix(cli/mcp): refuse switch_context while indexing jobs are still active

* fix(php): extract promoted and variadic parameters (CodeGraphContext#1643)

* fix(cli/mcp): stop path→repo_path alias from breaking calculate_cyclomatic_complexity (CodeGraphContext#1638)

---------

Co-authored-by: Yash <C0deRator@proton.me>
Co-authored-by: Shashank Shekhar Singh <Shashankshekharsingh1205@gmail.com>

* fix(graph): re-absolutize repo-scoped bundle paths on import so a second bundle is not treated as a duplicate (CodeGraphContext#1627)

* fix: index single-line Kotlin objects (CodeGraphContext#1610)

* ci: add cross-OS E2E tests and gracefully skip missing backends (CodeGraphContext#1635)

* ci: add cross-OS E2E tests and gracefully skip missing backends

* fix(php): extract promoted and variadic parameters (CodeGraphContext#1643)

* fix(cli/mcp): stop path→repo_path alias from breaking calculate_cyclomatic_complexity (CodeGraphContext#1638)

* fix(cli/mcp): refuse switch_context while indexing jobs are still active (CodeGraphContext#1641)

* fix(cli/mcp): refuse switch_context while indexing jobs are still active

* fix(php): extract promoted and variadic parameters (CodeGraphContext#1643)

* fix(cli/mcp): stop path→repo_path alias from breaking calculate_cyclomatic_complexity (CodeGraphContext#1638)

---------

Co-authored-by: Yash <C0deRator@proton.me>
Co-authored-by: Shashank Shekhar Singh <Shashankshekharsingh1205@gmail.com>

* fix(graph): re-absolutize repo-scoped bundle paths on import so a second bundle is not treated as a duplicate (CodeGraphContext#1627)

* fix pkg_map: falkordb import name is 'falkordb', not 'falkordblite'

---------

Co-authored-by: Yash <C0deRator@proton.me>
Co-authored-by: Ashmeet Singh Sandhu <sandhuashmeet40@gmail.com>
Co-authored-by: Shashank Shekhar Singh <Shashankshekharsingh1205@gmail.com>

* fix: normalize repo_path filters so bare repo names actually match (CodeGraphContext#1633) (CodeGraphContext#1634)

Every repo_path-scoped query in CodeFinder built its Cypher filter as a raw
`node.path STARTS WITH $repo_path` string comparison with no normalization.
Node paths are stored as the absolute filesystem path used at indexing time,
so passing anything else — the repo name from list_indexed_repositories(),
a relative path, a trailing slash — silently matched zero nodes, with no
error surfaced to the caller.

Adds _normalize_repo_path_filter(), which resolves a non-absolute repo_path
against the indexed repository list (by name, basename, or cwd-relative
path) before it reaches any STARTS WITH filter. Wired into every method on
CodeFinder that accepts repo_path. Also replaces the unrelated but similarly
broken Path(repo_path).resolve() in audit_kotlin_call_ambiguity, which
resolved against the server process cwd instead of the indexed root.

Verified against a local KuzuDB index containing both a Python and a Java
repo: short repo names now resolve to the correct absolute root and return
the expected matches, while cross-repo scoping still correctly excludes the
other repo's results.

Fixes CodeGraphContext#1633.

Co-authored-by: paul.mathieson <paul.mathieson@modulrfinance.com>

* fix(resolution): preserve function name for attribute-qualified module calls (Fixes CodeGraphContext#1573) (CodeGraphContext#1636)

* Fixes CodeGraphContext#1542: Parameterize relationship query limits, add truncation flags & CLI support

* fix(resolution): don't clobber resolved_called_name for attribute-qualified calls in resolve_function_call()

import m; m.fn() and import m as t; t.fn() produced zero CALLS/HEURISTIC_CALLS edges because the import-alias fallback in resolve_function_call() treated the receiver name (lookup_name == base_obj) as if it were the callee's own name, overwriting resolved_called_name with the module/receiver name. The later writer.py MATCH (by called_name) then found no matching Function node and silently dropped the edge. Guard the overwrite so it only applies to genuine direct-call import aliases (lookup_name == called_name), fixing Python module.fn()/module_alias.fn() call resolution without touching any other resolution tier.

Fixes CodeGraphContext#1573

* fix(resolution): allow import-alias module path lookup for attribute calls without overwriting called_name

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Yash <C0deRator@proton.me>
Co-authored-by: Ashmeet Singh Sandhu <sandhuashmeet40@gmail.com>
Co-authored-by: Shashank Shekhar Singh <Shashankshekharsingh1205@gmail.com>
Co-authored-by: mjq2020 <74635395+mjq2020@users.noreply.github.com>
Co-authored-by: Soham Gangopadhyay <sohamgangopadhyay2007@gmail.com>
Co-authored-by: pm-dun86 <pm@dunedincouriers.com>
Co-authored-by: paul.mathieson <paul.mathieson@modulrfinance.com>
Co-authored-by: Boda shanmukha datta <shanmukhadattaboda069@gmail.com>
…eGraphContext#1640)

* fix(imports): record from-import source module and edge language (CodeGraphContext#1639)

from X import Y was storing a Module named Y. Keep the Module as X, put Y
on the IMPORTS edge, and stamp r.lang so Python queries can filter out
JS/TS specifiers that share the same Module name.

* fix(php): extract promoted and variadic parameters (CodeGraphContext#1643)

* fix(cli/mcp): stop path→repo_path alias from breaking calculate_cyclomatic_complexity (CodeGraphContext#1638)

* fix(cli/mcp): refuse switch_context while indexing jobs are still active (CodeGraphContext#1641)

* fix(cli/mcp): refuse switch_context while indexing jobs are still active

* fix(php): extract promoted and variadic parameters (CodeGraphContext#1643)

* fix(cli/mcp): stop path→repo_path alias from breaking calculate_cyclomatic_complexity (CodeGraphContext#1638)

---------

Co-authored-by: Yash <C0deRator@proton.me>
Co-authored-by: Shashank Shekhar Singh <Shashankshekharsingh1205@gmail.com>

* fix(graph): re-absolutize repo-scoped bundle paths on import so a second bundle is not treated as a duplicate (CodeGraphContext#1627)

* fix: index single-line Kotlin objects (CodeGraphContext#1610)

* ci: add cross-OS E2E tests and gracefully skip missing backends (CodeGraphContext#1635)

* ci: add cross-OS E2E tests and gracefully skip missing backends

* fix(php): extract promoted and variadic parameters (CodeGraphContext#1643)

* fix(cli/mcp): stop path→repo_path alias from breaking calculate_cyclomatic_complexity (CodeGraphContext#1638)

* fix(cli/mcp): refuse switch_context while indexing jobs are still active (CodeGraphContext#1641)

* fix(cli/mcp): refuse switch_context while indexing jobs are still active

* fix(php): extract promoted and variadic parameters (CodeGraphContext#1643)

* fix(cli/mcp): stop path→repo_path alias from breaking calculate_cyclomatic_complexity (CodeGraphContext#1638)

---------

Co-authored-by: Yash <C0deRator@proton.me>
Co-authored-by: Shashank Shekhar Singh <Shashankshekharsingh1205@gmail.com>

* fix(graph): re-absolutize repo-scoped bundle paths on import so a second bundle is not treated as a duplicate (CodeGraphContext#1627)

* fix pkg_map: falkordb import name is 'falkordb', not 'falkordblite'

---------

Co-authored-by: Yash <C0deRator@proton.me>
Co-authored-by: Ashmeet Singh Sandhu <sandhuashmeet40@gmail.com>
Co-authored-by: Shashank Shekhar Singh <Shashankshekharsingh1205@gmail.com>

* fix: normalize repo_path filters so bare repo names actually match (CodeGraphContext#1633) (CodeGraphContext#1634)

Every repo_path-scoped query in CodeFinder built its Cypher filter as a raw
`node.path STARTS WITH $repo_path` string comparison with no normalization.
Node paths are stored as the absolute filesystem path used at indexing time,
so passing anything else — the repo name from list_indexed_repositories(),
a relative path, a trailing slash — silently matched zero nodes, with no
error surfaced to the caller.

Adds _normalize_repo_path_filter(), which resolves a non-absolute repo_path
against the indexed repository list (by name, basename, or cwd-relative
path) before it reaches any STARTS WITH filter. Wired into every method on
CodeFinder that accepts repo_path. Also replaces the unrelated but similarly
broken Path(repo_path).resolve() in audit_kotlin_call_ambiguity, which
resolved against the server process cwd instead of the indexed root.

Verified against a local KuzuDB index containing both a Python and a Java
repo: short repo names now resolve to the correct absolute root and return
the expected matches, while cross-repo scoping still correctly excludes the
other repo's results.

Fixes CodeGraphContext#1633.

Co-authored-by: paul.mathieson <paul.mathieson@modulrfinance.com>

* fix(resolution): preserve function name for attribute-qualified module calls (Fixes CodeGraphContext#1573) (CodeGraphContext#1636)

* Fixes CodeGraphContext#1542: Parameterize relationship query limits, add truncation flags & CLI support

* fix(resolution): don't clobber resolved_called_name for attribute-qualified calls in resolve_function_call()

import m; m.fn() and import m as t; t.fn() produced zero CALLS/HEURISTIC_CALLS edges because the import-alias fallback in resolve_function_call() treated the receiver name (lookup_name == base_obj) as if it were the callee's own name, overwriting resolved_called_name with the module/receiver name. The later writer.py MATCH (by called_name) then found no matching Function node and silently dropped the edge. Guard the overwrite so it only applies to genuine direct-call import aliases (lookup_name == called_name), fixing Python module.fn()/module_alias.fn() call resolution without touching any other resolution tier.

Fixes CodeGraphContext#1573

* fix(resolution): allow import-alias module path lookup for attribute calls without overwriting called_name

* fix: five independent bugs from CodeGraphContext#1595 (CodeGraphContext#1600, CodeGraphContext#1601, CodeGraphContext#1606, CodeGraphContext#1607, CodeGraphContext#1608) (CodeGraphContext#1632)

* test(macos): resolve the tmp path in the Kotlin parser fixture (CodeGraphContext#1608)

Three tests in TestKotlinFunctionCallResolution failed on a clean macOS
checkout: tempfile hands back /var/folders/... while /var is a symlink to
/private/var, and the call-resolution layer stores the fully resolved
path. Any assertion comparing an edge's called_file_path against
Path(data["path"]).as_posix() therefore compared /private/var/... to
/var/... and failed.

Fixed in _write_and_parse rather than in the assertions. There are 38
such comparison sites in this file; only 3 happened to be reachable in a
shape that tripped the mismatch, so patching those 3 would have left the
other 35 latent for the next test that resolves a path. Resolving once,
before parsing, makes data["path"] canonical everywhere and removes the
need for each call site to remember .resolve().

Not a regression -- it reproduces on a clean tree -- but it made a fresh
macOS checkout look broken and could mask real failures.

tests/unit: 1225 passed, 19 skipped, 0 failed (was 3 failed, 1222 passed).

The same helper shape exists in test_java_parser.py and
test_java_package_qualified_names.py. Those are green, so they are left
alone rather than changed speculatively.

Closes CodeGraphContext#1608

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(writer): land DECORATED_BY edges for decorated classes (CodeGraphContext#1601)

DECORATED_BY is declared as a REL TABLE GROUP with two pairs --
`FROM Function TO Function, FROM Class TO Function` -- and
build_decorated_by_links emits rows for decorated classes correctly. The
writer hardcoded `:Function` on the decorated endpoint, so every class row
matched nothing and was dropped with no error and no log.

The silent drop had two layers, and fixing only the first would have
looked correct while changing nothing:

1. The hardcoded label. Now iterates the declared source labels
   ("Function", "Class") and stops at the first that matches real
   endpoints, exactly as write_binds_links does -- a row carries
   name+path+line but no label, so a same-named node under the other
   label would otherwise pick up a second, spurious edge.

2. The `context` predicate. `Class` has no `context` column, so
   `WHERE ... decorated.context = $decorated_context` raises "Binder
   exception: Cannot find property context for decorated" -- which
   _is_binder_exception catches and swallows. Parameterising the label
   alone still dropped every class row, one layer further down. The
   predicate is now emitted only for the Function label. That is exact,
   not approximate: build_decorated_by_links only ever sets
   decorated_context from a function's class_context and leaves it "" for
   classes, so the predicate was a no-op there regardless.

Effect is not Kotlin-specific -- it is every language that records
class-level decorators. The TypeScript golden gains 4 edges that were
being dropped:

    Class:User        -[DECORATED_BY]-> Function:Entity
    Class:User        -[DECORATED_BY]-> Function:Injectable
    Class:User        -[DECORATED_BY]-> Function:Serializable
    Class:UserService -[DECORATED_BY]-> Function:Component

Only the TypeScript golden is refreshed, and its normalized delta is
exactly those 4 additions: 0 edges removed, 0 node changes. Verified by
diffing through the golden test's own load_and_normalize rather than by
raw file diff -- the raw .jsonl files churn between regenerations
(internal node offsets, and CALLS/HEURISTIC_CALLS reclassification, which
normalization discards by design), so a raw diff would have buried the
signal. Confirmed that regenerating on unmodified main rewrites 62 golden
files while all 21 golden tests still pass, which is why the other
goldens are deliberately left untouched rather than bulk-regenerated.

Test drives the real chain -- parser, builder, writer, query -- because
the defect is precisely that correctly-built rows never land. It uses
Kotlin rather than Python: Python's parser does not populate `decorators`
on classes at all (`@my_decorator class C` yields `[]`), a separate
parser gap that is not this bug and is left unfiled here.

tests/unit: 1228 passed, 19 skipped.
tests/integration: 45 passed.

Closes CodeGraphContext#1601

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(tools): stop find_dead_code capping itself at 50 results (CodeGraphContext#1606)

The query ended with a hardcoded `LIMIT 50` applied *after* the decorator
filter, so excluded rows were backfilled by the next ones in path order
and the returned count was 50 either way. On the reporter's Android
codebase that presented as `exclude_decorated_with` doing nothing while it
was in fact removing 6,086 of 7,141 false positives.

Three consequences, all fixed here:

- The total was unobtainable at any call site. find_dead_code now returns
  `total_count` alongside the page, unconditionally -- a count that
  appears only when truncated is nearly as unusable as no count.
- The handler's pagination was starved. analysis_handlers already read
  TOOL_RESULT_LIMITS and set truncated/result_limit, but never saw more
  than the 50 rows the query returned, so configuring a higher limit
  changed nothing. It now pages a real result set.
- `limit` is a parameter rather than a constant, defaulting to None (all
  rows) at the finder and to the configured tool limit at each caller.

The CLI is updated in the same commit rather than left to follow. Its
table had no page size of its own -- the query's `LIMIT 50` was acting as
one by accident -- so unbounding the finder alone would have printed
several thousand table rows on a real codebase. It now requests the
configured limit explicitly and prints the true total, which is the number
the issue was actually asking for:

    Total: 7141 function(s); showing the first 50 by path

Not done deliberately: the issue's third suggestion, ordering so the
result samples rather than clusters. Ordering is still path then line, so
a limited result is a path-ordered prefix concentrated in the first few
files. That is a product decision about what a truncated sample should
mean rather than a defect, and it is now documented on the method instead
of being silent.

Tests drive the real parser -> writer -> query chain with 70 dead
functions, 30 of them annotated -- above the old cap, and split so the
filtered count (40) coincides with neither 50 nor the unfiltered total,
so a still-capped result cannot pass by accident.

tests/unit: 1233 passed, 19 skipped.
tests/integration: 45 passed.

Closes CodeGraphContext#1606

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(parsers/kotlin): index single-line objects (CodeGraphContext#1600)

The tree-sitter Kotlin grammar misparses `object A { fun x() = 1 }`. It
produces

    infix_expression
      object_literal ("object")
      simple_identifier ("A")
      lambda_literal { statements { function_declaration } }

rather than an `object_declaration`, so the pattern the classes query
looks for is never produced and the singleton is silently missing from
the graph. `object A { }` and the multi-line form parse correctly; only
the one-line-with-a-body shape is affected, and it is a common Kotlin
idiom.

Fixed in three places, because capturing the node alone would have looked
complete while leaving members unattributed:

- The classes query gains an arm for the misparse shape.
- _parse_classes records it under "objects"/Object; without that it would
  fall through to the class_declaration branch and be stored as a Class.
- _get_parent_context and _get_enclosing_class_context recognise it as an
  enclosing scope. This is the half that is easy to miss: the members of
  a misparsed object have no object_declaration ancestor, so `fun x`
  landed at top level with context None while the identical multi-line
  object gives its members context "A".

`object_literal` as the left operand is the discriminator, and it is what
makes matching an `infix_expression` safe: object_literal can only come
from the `object` keyword, so an ordinary infix call with a trailing
lambda (`someValue apply { ... }`) is not captured. There is a boundary
test for exactly that, plus a regression test that the multi-line and
empty forms still produce one object each rather than being duplicated by
the new arm.

Also checked and deliberately not changed: the single-line companion form
`companion object { fun z() = 3 }` parses as a real companion_object and
was never affected. It is pinned by a test so a future change to the new
arm cannot start double-counting it.

tests/unit: 1238 passed, 19 skipped.
tests/integration: 45 passed, no golden changes.

Closes CodeGraphContext#1600

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(writer): store empty list properties as [] rather than [""] (CodeGraphContext#1607)

A function with no decorators persisted `decorators == [""]`, and the same
applied to `args`, `modifiers` and every other list-valued property in
every language. It did not corrupt query results -- `"" CONTAINS
'Preview'` is false, so find_dead_code still retained un-annotated
functions correctly -- but `[""]` is not what any caller means by "none",
and it forced every consumer to special-case a one-element list holding
the empty string.

The issue asked whether the `else [""]` branch was working around a
backend that rejects empty lists, in which case the fix would belong at
the read boundary instead. It is not. Kùzu 0.11.3 accepts `[]` for a
STRING[] column in every shape this writer uses: inside `UNWIND $rows AS
row`, as a single-row parameter, and when *every* row in the batch is
empty so there is no sibling row to infer an element type from. The
dominant-type inference just above is unaffected too -- it only skips
None, and [] is not None, so an always-empty key still resolves to
"list". Both cases have tests; the all-empty batch is the one that would
plausibly have motivated a sentinel.

Golden refresh, 18 of 21 projects. The delta was measured through the
golden test's own load_and_normalize rather than by raw file diff, and it
is exactly 641 node properties changing [""] -> [], with 0 nodes added or
removed and 0 edges added or removed:

    sample_project             80    sample_project_javascript   40
    sample_project_c            3    sample_project_kotlin      110
    sample_project_cpp         26    sample_project_lua           4
    sample_project_csharp      17    sample_project_perl         11
    sample_project_dart        19    sample_project_ruby         12
    sample_project_elixir       3    sample_project_rust         77
    sample_project_go         104    sample_project_scala         4
    sample_project_haskell      3    sample_project_swift        22
    sample_project_java        14    sample_project_typescript   92

The three goldens with no checked change (elisp, misc, php) were reverted
rather than left churning, and they are exactly the three that passed the
integration suite before the refresh -- which cross-checks the
measurement.

Measuring through the normalizer rather than by raw diff is necessary
here, not fastidious: regenerating goldens on unmodified main rewrites 62
files while all 21 golden tests still pass, because the raw .jsonl
carries internal node offsets and load_and_normalize discards
CALLS/HEURISTIC_CALLS edges by design. The property comparison is
intersection-based, matching the check's own
`common_keys = set(exp_node).intersection(act_node)`; the committed
goldens are additionally missing properties the current code emits
(language, is_dependency, visibility), which the check skips and which
this commit deliberately does not touch.

tests/unit: 1240 passed, 19 skipped.
tests/integration: 45 passed.

Closes CodeGraphContext#1607

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(php): extract promoted and variadic parameters (CodeGraphContext#1643)

* fix(cli/mcp): stop path→repo_path alias from breaking calculate_cyclomatic_complexity (CodeGraphContext#1638)

* fix(cli/mcp): refuse switch_context while indexing jobs are still active (CodeGraphContext#1641)

* fix(cli/mcp): refuse switch_context while indexing jobs are still active

* fix(php): extract promoted and variadic parameters (CodeGraphContext#1643)

* fix(cli/mcp): stop path→repo_path alias from breaking calculate_cyclomatic_complexity (CodeGraphContext#1638)

---------

Co-authored-by: Yash <C0deRator@proton.me>
Co-authored-by: Shashank Shekhar Singh <Shashankshekharsingh1205@gmail.com>

* fix(graph): re-absolutize repo-scoped bundle paths on import so a second bundle is not treated as a duplicate (CodeGraphContext#1627)

* fix: index single-line Kotlin objects (CodeGraphContext#1610)

* ci: add cross-OS E2E tests and gracefully skip missing backends (CodeGraphContext#1635)

* ci: add cross-OS E2E tests and gracefully skip missing backends

* fix(php): extract promoted and variadic parameters (CodeGraphContext#1643)

* fix(cli/mcp): stop path→repo_path alias from breaking calculate_cyclomatic_complexity (CodeGraphContext#1638)

* fix(cli/mcp): refuse switch_context while indexing jobs are still active (CodeGraphContext#1641)

* fix(cli/mcp): refuse switch_context while indexing jobs are still active

* fix(php): extract promoted and variadic parameters (CodeGraphContext#1643)

* fix(cli/mcp): stop path→repo_path alias from breaking calculate_cyclomatic_complexity (CodeGraphContext#1638)

---------

Co-authored-by: Yash <C0deRator@proton.me>
Co-authored-by: Shashank Shekhar Singh <Shashankshekharsingh1205@gmail.com>

* fix(graph): re-absolutize repo-scoped bundle paths on import so a second bundle is not treated as a duplicate (CodeGraphContext#1627)

* fix pkg_map: falkordb import name is 'falkordb', not 'falkordblite'

---------

Co-authored-by: Yash <C0deRator@proton.me>
Co-authored-by: Ashmeet Singh Sandhu <sandhuashmeet40@gmail.com>
Co-authored-by: Shashank Shekhar Singh <Shashankshekharsingh1205@gmail.com>

* fix: normalize repo_path filters so bare repo names actually match (CodeGraphContext#1633) (CodeGraphContext#1634)

Every repo_path-scoped query in CodeFinder built its Cypher filter as a raw
`node.path STARTS WITH $repo_path` string comparison with no normalization.
Node paths are stored as the absolute filesystem path used at indexing time,
so passing anything else — the repo name from list_indexed_repositories(),
a relative path, a trailing slash — silently matched zero nodes, with no
error surfaced to the caller.

Adds _normalize_repo_path_filter(), which resolves a non-absolute repo_path
against the indexed repository list (by name, basename, or cwd-relative
path) before it reaches any STARTS WITH filter. Wired into every method on
CodeFinder that accepts repo_path. Also replaces the unrelated but similarly
broken Path(repo_path).resolve() in audit_kotlin_call_ambiguity, which
resolved against the server process cwd instead of the indexed root.

Verified against a local KuzuDB index containing both a Python and a Java
repo: short repo names now resolve to the correct absolute root and return
the expected matches, while cross-repo scoping still correctly excludes the
other repo's results.

Fixes CodeGraphContext#1633.

Co-authored-by: paul.mathieson <paul.mathieson@modulrfinance.com>

* fix(resolution): preserve function name for attribute-qualified module calls (Fixes CodeGraphContext#1573) (CodeGraphContext#1636)

* Fixes CodeGraphContext#1542: Parameterize relationship query limits, add truncation flags & CLI support

* fix(resolution): don't clobber resolved_called_name for attribute-qualified calls in resolve_function_call()

import m; m.fn() and import m as t; t.fn() produced zero CALLS/HEURISTIC_CALLS edges because the import-alias fallback in resolve_function_call() treated the receiver name (lookup_name == base_obj) as if it were the callee's own name, overwriting resolved_called_name with the module/receiver name. The later writer.py MATCH (by called_name) then found no matching Function node and silently dropped the edge. Guard the overwrite so it only applies to genuine direct-call import aliases (lookup_name == called_name), fixing Python module.fn()/module_alias.fn() call resolution without touching any other resolution tier.

Fixes CodeGraphContext#1573

* fix(resolution): allow import-alias module path lookup for attribute calls without overwriting called_name

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Yash <C0deRator@proton.me>
Co-authored-by: Ashmeet Singh Sandhu <sandhuashmeet40@gmail.com>
Co-authored-by: Shashank Shekhar Singh <Shashankshekharsingh1205@gmail.com>
Co-authored-by: mjq2020 <74635395+mjq2020@users.noreply.github.com>
Co-authored-by: Soham Gangopadhyay <sohamgangopadhyay2007@gmail.com>
Co-authored-by: pm-dun86 <pm@dunedincouriers.com>
Co-authored-by: paul.mathieson <paul.mathieson@modulrfinance.com>
Co-authored-by: Boda shanmukha datta <shanmukhadattaboda069@gmail.com>

* regenerate sample_project goldens on merged tree (from-import fix + empty-list fix)

---------

Co-authored-by: Yash <C0deRator@proton.me>
Co-authored-by: Ashmeet Singh Sandhu <sandhuashmeet40@gmail.com>
Co-authored-by: Shashank Shekhar Singh <Shashankshekharsingh1205@gmail.com>
Co-authored-by: mjq2020 <74635395+mjq2020@users.noreply.github.com>
Co-authored-by: Soham Gangopadhyay <sohamgangopadhyay2007@gmail.com>
Co-authored-by: pm-dun86 <pm@dunedincouriers.com>
Co-authored-by: paul.mathieson <paul.mathieson@modulrfinance.com>
Co-authored-by: Boda shanmukha datta <shanmukhadattaboda069@gmail.com>
Co-authored-by: Ricardo R. Rodrigues <ricardorodrigues@nutrium.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat: created dedicated contributing page

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix: correct import path for Contributing page

* fix: correct template string usage in markdown conversion

* restore package.json/package-lock.json from main (no dependency changes needed)

* restore footer maintainer attribution; fix h2 template literal so headings interpolate

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Shashank Shekhar Singh <Shashankshekharsingh1205@gmail.com>
…x in Kuzu schema, uid_map, SCHEMA_MAP and bundle uid parts, with real-Kuzu regression tests
…e repeated Variable mentions, regen misc golden
Shashankss1205
Shashankss1205 previously approved these changes Aug 18, 2026

@Shashankss1205 Shashankss1205 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strong analysis and the right identity design — ordinal-0 keeps every non-colliding node's identity unchanged, the constraint migration (drop legacy names, recreate under new names) is startup-safe, and your Annotation observation was correct.

The PR as submitted fixed Neo4j but not the embedded backends: on KuzuDB/LadybugDB these tables are keyed on a computed uid, and the compat layer still built that uid from (name, path, line_number) — so colliding symbols kept collapsing on the default backend, and occurrence_index wasn't a declared column (writes would silently drop it; MATCHes on labels without the column errored and lost CONTAINS edges — 7 of 21 language goldens caught this). I completed it on your branch:

  • occurrence_index INT64 columns + startup migrations for all 15 positionally-keyed labels (your 11 + EnumMember/Mixin/Extension/Object, which are also in item_mappings)
  • uid_map/SCHEMA_MAP/bundle _UID_PARTS extended so uid = name+path+line+ordinal everywhere, with a 0 default for legacy write paths
  • Variable records are coalesced, not split: extractors emit one record per mention ($i = 0; $i < n; $i++ is three records for one symbol), so same-key Variable records merge last-write-wins instead of minting duplicate nodes
  • real-Kuzu regression tests (collision → 2 nodes with distinct uids and preserved properties; re-write idempotence), plus the canonical tfoot CSS split regenerated into the misc golden

Final state: 1312 unit tests green, full integration suite green (51, incl. all 21 goldens). Thanks @Falehaqazi — closes #1393.

@Falehaqazi
Falehaqazi dismissed Shashankss1205’s stale review August 18, 2026 18:25

The merge-base changed after approval.

@Shashankss1205
Shashankss1205 merged commit 06e56bb into CodeGraphContext:main Aug 18, 2026
1 check failed
@github-project-automation github-project-automation Bot moved this from Backlog tasks to Done in CGC Progress Board Aug 18, 2026
@Shashankss1205 Shashankss1205 added gssoc:approved GSSoC validation: counts toward scoring level:advanced GSSoC difficulty: 55 pts contributor / 30 mentor mentor:Shashankss1205 GSSoC mentor attribution: credits reviewing mentor type:bug GSSoC type bonus: bug labels Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc:approved GSSoC validation: counts toward scoring level:advanced GSSoC difficulty: 55 pts contributor / 30 mentor mentor:Shashankss1205 GSSoC mentor attribution: credits reviewing mentor type:bug GSSoC type bonus: bug

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

bug(indexer): Node identity (name, path, line_number) is not unique; symbols merge and properties are clobbered