Skip to content

feat: polyglot multilang parse (roadmap #18 MVP)#19

Merged
lemduc merged 5 commits into
mainfrom
feat/polyglot-multilang-parse
Jul 24, 2026
Merged

feat: polyglot multilang parse (roadmap #18 MVP)#19
lemduc merged 5 commits into
mainfrom
feat/polyglot-multilang-parse

Conversation

@tuannx

@tuannx tuannx commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds polyglot ingest/parse via languages=[...] or language="multi": discovers Maven dual roots (src/main/java + src/main/kotlin), merges per-language graphs, and relinks import / extends / implements across the combined FQN space (Java↔Kotlin first; same mechanism for other same-FQN / import-linked pairs).
  • Wires MCP ingest/parse and CI analyze action / reusable workflow with a languages input (comma-separated); single-language callers remain backward compatible.
  • Marks roadmap feat(parser): add Rust support #18 as MVP done; RPC/IDL bridges (gRPC, OpenAPI) explicitly remain follow-up. README capability row updated accordingly.
  • Review fix: cross-language FQN collisions now keep the first entity (matches documented behavior; previously DependencyGraph.merge kept the second).
  • E2E coverage: full ingest→parse→recover, MCP session pipeline, and CLI --languages self-analysis on Java+Kotlin fixtures.

Test plan

  • ruff check on touched multilang sources/tests
  • pytest tests/test_parsers/test_graph_merge.py tests/test_tools/test_ingest_multilang.py tests/test_tools/test_parse_multilang.py (13 passed)
  • pytest tests/test_parsers/ tests/test_tools/ (118 passed)
  • pytest tests/test_tools/test_pipeline_multilang_e2e.py tests/test_mcp_multilang_e2e.py tests/test_tools/test_self_analysis_multilang.py (+ existing multilang suite: 19 passed)
  • Optional: run analyze action with languages: java,kotlin on a mixed Maven repo
  • Embabel-agent: not re-validated end-to-end in this PR; Kotlin embabel-pattern unit fixtures from feat: add Kotlin parser with careful fixtures and tests #17 remain on main and were not regressively hit by these changes (parser suite green)

Notes

  • Junk from feature/multilang-parse (meeting notes, uv.lock, patches, etc.) intentionally excluded.
  • Residual risks: simple-name fqn_index fallback can mis-resolve ambiguous short names across packages; no gRPC/OpenAPI edge bridging yet.

Made with Cursor

Copilot AI review requested due to automatic review settings July 20, 2026 04:05
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

🤖 Architecture Analysis Summary

Powered by arcade-agent — automatic architectural self-analysis


📈 Metric Evolution

Baseline commit: 746fc56

Legend: 🟢 better · 🔴 worse · 🟡 low impact · ⚪ no change

Metric Baseline Current Change
BalancedArchitectureScore 0.6193 0.6229 🟢 ↑ (+0.0036)
📦 Components 9 9 → (no change)
🧩 Entities 115 120 🟡 ↑ (+5.0000)
🔗 Edges 115 120 🟡 ↑ (+5.0000)
🏷️ Classes 22 22 → (no change)
ƒ Functions 271 289 🟡 ↑ (+18.0000)
🔧 Methods 34 34 → (no change)
RCI 0.5652 0.5833 🟢 ↑ (+0.0181)
TurboMQ 0.2493 0.2504 🟢 ↑ (+0.0011)
BasicMQ 0.2493 0.2504 🟢 ↑ (+0.0011)
IntraConnectivity 0.0182 0.0138 🟡 ↓ (-0.0044)
InterConnectivity 0.0323 0.0315 🟢 ↓ (-0.0008)
TwoWayPairRatio 0.0000 0.0000 → (no change)
DependencyHealth 0.9822 0.9827 🟢 ↑ (+0.0005)
ComponentBalance 0.5304 0.5315 🟢 ↑ (+0.0011)
HubBalance 0.5000 0.5000 → (no change)
BoundaryClarity 0.9444 0.9444 → (no change)
DependencyDistribution 0.6263 0.6263 → (no change)
SmellDiscipline 0.9333 0.9333 → (no change)
PrincipleAlignmentScore 0.8553 0.8555 🟢 ↑ (+0.0002)

🏛️ Current Architecture

Metric Value
📦 Components 9
🧩 Entities 120
🔗 Edges 120
🏷️ Classes 22
ƒ Functions 289
🔧 Methods 34
Balanced Score 🟡 0.6229 (Fair)
Principle Alignment 0.8555
RCI 0.5833
TurboMQ 0.2504
BasicMQ 0.2504
IntraConnectivity 0.0138
InterConnectivity 0.0315
TwoWayPairRatio 0.0000
DependencyHealth 0.9827
ComponentBalance 0.5315
HubBalance 0.5000
BoundaryClarity 0.9444
DependencyDistribution 0.6263
SmellDiscipline 0.9333

🧭 Principle Signals

Signal Value
AcyclicDependencies 1.0000
LayeringHealth 0.9870
ResponsibilityFocus 0.9333
InterfaceSegregation 1.0000
ComponentBalance 0.5315
HubBalance 0.5000
BoundaryClarity 0.9444
DependencyDistribution 0.6263
SmellDiscipline 0.9333

🎯 Score Drivers

Biggest risks

  • HubBalance: gap=0.5000 (signal=0.5000)
  • ComponentBalance: gap=0.4685 (signal=0.5315)
  • DependencyDistribution: gap=0.3737 (signal=0.6263)

Strongest areas

  • AcyclicDependencies: gap=0.0000 (signal=1.0000)
  • InterfaceSegregation: gap=0.0000 (signal=1.0000)
  • LayeringHealth: gap=0.0130 (signal=0.9870)

🕸️ High-Level Design

graph TD
    Algorithms["Algorithms\n39 entities\n5 classes / 3 methods"]
    Budget["Budget\n3 entities\n0 classes / 0 methods"]
    Cache["Cache\n4 entities\n0 classes / 0 methods"]
    Ci["Ci\n5 entities\n0 classes / 0 methods"]
    Exporters["Exporters\n14 entities\n1 classes / 0 methods"]
    Incremental["Incremental\n2 entities\n1 classes / 2 methods"]
    Parsers["Parsers\n22 entities\n11 classes / 27 methods"]
    Serialization["Serialization\n8 entities\n0 classes / 0 methods"]
    Tools["Tools\n23 entities\n4 classes / 2 methods"]
    Cache --> Serialization
    Ci --> Algorithms
    Ci --> Exporters
    Ci --> Serialization
    Ci --> Tools
    Serialization --> Algorithms
    Serialization --> Parsers
    Tools --> Algorithms
    Tools --> Cache
    Tools --> Exporters
    Tools --> Parsers
Loading
🏗️ Components breakdown
Component Entities Classes Methods
Algorithms 39 5 3
Tools 23 4 2
Parsers 22 11 27
Exporters 14 1 0
Serialization 8 0 0
Ci 5 0 0
Cache 4 0 0
Budget 3 0 0
Incremental 2 1 2

🚨 Architectural Smells

Severity Type Affected Components
🟡 medium Concern Overload Tools

📈 Evolution vs Baseline

Baseline commit: 746fc56

Architecture-to-Architecture (A2A) Comparison

Metric Value
A2A Similarity 0.9747
Matched Components 9
Components Added 0
Components Removed 0
Component matching details

Matched:

Baseline Current Similarity
Algorithms Algorithms 1.0000
Budget Budget 1.0000
Cache Cache 1.0000
Ci Ci 1.0000
Exporters Exporters 1.0000
Incremental Incremental 1.0000
Serialization Serialization 1.0000
Tools Tools 1.0000
Parsers Parsers 0.7727
High-level component statistics
Status Baseline Current Similarity Entities Classes Methods
matched Algorithms Algorithms 1.0000 39 → 39 (0) 5 → 5 (0) 3 → 3 (0)
matched Budget Budget 1.0000 3 → 3 (0) 0 → 0 (0) 0 → 0 (0)
matched Cache Cache 1.0000 4 → 4 (0) 0 → 0 (0) 0 → 0 (0)
matched Ci Ci 1.0000 5 → 5 (0) 0 → 0 (0) 0 → 0 (0)
matched Exporters Exporters 1.0000 14 → 14 (0) 1 → 1 (0) 0 → 0 (0)
matched Incremental Incremental 1.0000 2 → 2 (0) 1 → 1 (0) 2 → 2 (0)
matched Parsers Parsers 0.7727 17 → 22 (+5) 11 → 11 (0) 27 → 27 (0)
matched Serialization Serialization 1.0000 8 → 8 (0) 0 → 0 (0) 0 → 0 (0)
matched Tools Tools 1.0000 23 → 23 (0) 4 → 4 (0) 2 → 2 (0)
Before/After Mermaid diagrams

Baseline

graph TD
    Algorithms["Algorithms\n39 entities\n5 classes / 3 methods"]
    Budget["Budget\n3 entities\n0 classes / 0 methods"]
    Cache["Cache\n4 entities\n0 classes / 0 methods"]
    Ci["Ci\n5 entities\n0 classes / 0 methods"]
    Exporters["Exporters\n14 entities\n1 classes / 0 methods"]
    Incremental["Incremental\n2 entities\n1 classes / 2 methods"]
    Parsers["Parsers\n17 entities\n11 classes / 27 methods"]
    Serialization["Serialization\n8 entities\n0 classes / 0 methods"]
    Tools["Tools\n23 entities\n4 classes / 2 methods"]
    Cache --> Serialization
    Ci --> Algorithms
    Ci --> Exporters
    Ci --> Serialization
    Ci --> Tools
    Serialization --> Algorithms
    Serialization --> Parsers
    Tools --> Algorithms
    Tools --> Cache
    Tools --> Exporters
    Tools --> Parsers
Loading

Current

graph TD
    Algorithms["Algorithms\n39 entities\n5 classes / 3 methods"]
    Budget["Budget\n3 entities\n0 classes / 0 methods"]
    Cache["Cache\n4 entities\n0 classes / 0 methods"]
    Ci["Ci\n5 entities\n0 classes / 0 methods"]
    Exporters["Exporters\n14 entities\n1 classes / 0 methods"]
    Incremental["Incremental\n2 entities\n1 classes / 2 methods"]
    Parsers["Parsers\n22 entities\n11 classes / 27 methods"]
    Serialization["Serialization\n8 entities\n0 classes / 0 methods"]
    Tools["Tools\n23 entities\n4 classes / 2 methods"]
    Cache --> Serialization
    Ci --> Algorithms
    Ci --> Exporters
    Ci --> Serialization
    Ci --> Tools
    Serialization --> Algorithms
    Serialization --> Parsers
    Tools --> Algorithms
    Tools --> Cache
    Tools --> Exporters
    Tools --> Parsers
Loading
Component dependency delta
Status Source Target
matched No dependency delta -

💡 CI/CD Insights

  • Quality Score: 🟡 Fair (BalancedArchitectureScore=0.6229)
  • Principle Alignment: 0.8555 (higher means cleaner layering, focus, and boundaries)
  • Top Risk Driver: HubBalance (signal=0.5000)
  • Trend: ➡️ Stable architectural quality
  • Architecture Stability: 🟢 High (A2A=0.9747)
  • Smells: ⚠️ 1 smell(s) — review suggested

📄 View HTML reports and artifacts


This comment is auto-generated by the self-dogfooding CI job. It updates on every push to this PR.

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Architecture Drift Report

Algorithm: PKG | Entities: 738 | Components: 7

Drift from Baseline

Metric Baseline Current Delta
Components 7 7 +0
Similarity 0.92
BalancedArchitectureScore 0.67
PrincipleAlignmentScore 0.83
RCI 0.99
TurboMQ 0.24
BasicMQ 0.24
IntraConnectivity 0.00
InterConnectivity 0.01
TwoWayPairRatio 0.00
DependencyHealth 1.00
ComponentBalance 0.23
HubBalance 0.67
BoundaryClarity 1.00
DependencyDistribution 0.57
SmellDiscipline 0.86

Changes

  • 68 entity movement(s) between components

Smells (1)

  • Concern Overload: Tests

Generated by arcade-agent

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an MVP polyglot workflow to arcade-agent: callers can now ingest/parse multiple languages in one run (via languages=[...] or language="multi"), merge the per-language graphs, and relink import/extends/implements edges across the combined FQN space (initially Java↔Kotlin). It also wires the new languages option through MCP and GitHub Actions, and updates docs/roadmap plus fixtures/tests to validate the behavior.

Changes:

  • Add cross-language DependencyGraph merge + edge relinking (merge_and_relink, relink_edges) to support polyglot dependency recovery.
  • Extend ingest/parse APIs (and MCP adapter + CI action/workflow + self-analysis runner) with a languages input while keeping single-language callers backward compatible.
  • Add fixtures and tests for multi-language Maven root discovery and Java↔Kotlin relinking.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/arcade_agent/tools/parse.py Adds languages/language="multi" support and multi-graph merge+relink orchestration.
src/arcade_agent/tools/ingest.py Adds multi-language discovery, stores languages, and preserves project root for multilang Maven layouts.
src/arcade_agent/parsers/multilang.py Implements graph union + relinking across combined entity space.
src/arcade_agent/parsers/graph.py Deduplicates merged package membership lists.
src/arcade_agent/tools/adapters/mcp.py Exposes languages through MCP ingest/parse and includes it in summaries.
src/arcade_agent/ci/run_self_analysis.py Adds --languages support and routes parsing accordingly.
actions/analyze/action.yml Adds languages input and passes it through to the runner.
.github/workflows/architecture-analysis-reusable.yml Adds languages input and passes it through to the runner.
tests/test_tools/test_parse_multilang.py New E2E tests for parse polyglot merge+relink and backward compatibility.
tests/test_tools/test_ingest_multilang.py New tests for multi-language ingest discovery and root selection behavior.
tests/test_parsers/test_graph_merge.py New tests for merge semantics and cross-language relinking behavior (incl. collision rule).
tests/fixtures/maven_java_kotlin/... Adds Maven-style Java/Kotlin fixture repo.
tests/fixtures/java_kotlin_mixed/... Adds mixed Java/Kotlin fixture repo validating relinked extends/implements.
README.md Documents polyglot merge+relink capability in the capabilities table.
ROADMAP.md Marks roadmap item #18 MVP as done and clarifies remaining follow-ups.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/arcade_agent/tools/parse.py
Comment thread src/arcade_agent/tools/ingest.py Outdated
@tuannx

tuannx commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator Author

Copilot review triage — fixed in f96c484fd6460d7504b8a335879531bfd4447bcb

Addressed both unresolved review threads:

  1. parse.py / detect_languages_from_files (discussion) — detect by Path.suffix without requiring paths to exist on disk.
  2. ingest.py / language validation (discussion) — fail-fast ValueError for unknown language overrides before discovery.

Both review threads replied + resolved. Multilang pytest suite: 15 passed.

@tuannx tuannx assigned tuannx and lemduc and unassigned tuannx and lemduc Jul 20, 2026
@tuannx
tuannx requested a review from lemduc July 20, 2026 13:53
@tuannx

tuannx commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up review completed in 80bc967:

  • made the polyglot MCP flow genuinely composable: ingest(..., languages=[...]) now returns a session that parse(source_path=<ingest-session>) can consume while inheriting the selected files/languages
  • added a real stdio MCP regression for Java + Kotlin session chaining
  • prevented cross-language relinking from mapping qualified external types or ambiguous simple names to unrelated local entities
  • documented the MCP polyglot install and workflow

Validation: 304 passed, Ruff clean, and a live stdio MCP client verified ingest -> parse -> get_full_result with Java/Kotlin FQNs. All PR checks are green.

@lemduc lemduc left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

The core Java↔Kotlin mechanism works and is well-tested (the Maven dual-root fixture with exact FQN/edge assertions is exactly right), and the axis I most expected to fail didn't: determinism is proven safe — I parsed the identical tree with main's code and this branch's code and got byte-identical output (325 entities, identical order, 176 identical edges), so existing single-language users' committed baselines don't churn. The _cache_language_key change also fixes a real pre-existing cache-miss bug. Good work on both.

The blocker is one layer up:

1. [Blocking] merge_and_relink is language-blind, and it fabricates cross-language edges. Three resolution paths in multilang.py ignore language boundaries: the unique-leaf fallback in resolve_name() (~L40-45), the import leaf fallback in relink_edges() (elif "." not in imp), and the exact-FQN import match. Reproduced on a Python+Java+Kotlin fixture:

  • Python class PaymentHandler(Base) with no local Baseapp.service.PaymentHandler -[extends]-> com.example.core.Base (python→java, pure fabrication via globally-unique leaf).
  • Python import com.auth.service (its own module) → import edge to a Java class with the coinciding FQN.

And it's not just graph noise — recover(algorithm="pkg")'s _refine_facade_groups moved the Python PaymentHandler into the JVM Core component while its own method stayed in App; WCA co-clustered a Python entity with the Kotlin Widget. This is the #17 leaf-fallback bug class reintroduced at the merge layer. Your negative tests cover qualified-external and ambiguous names but never cross-family pairs.

Suggested fix (small, and it resolves finding 2 as well): restrict merge+relink to compatible language families — JVM (java/kotlin) is the MVP's stated target anyway — or gate the fallbacks on the target entity's language being family-compatible with the source.

2. [Should fix] FQN collisions silently drop entities. No language namespacing: Python com/auth/service.py::login vs Java class service { login() } both produce com.auth.service.login; merge keeps first by language sort order (log-warning only), so the Python function vanished from the graph and its edges reattached to the Java method. For java↔kotlin same-FQN is arguably correct JVM semantics; cross-family it's silent data loss. Family-scoped merging fixes this too; at minimum surface a collision count in the result metadata — agents never see log lines.

3. [Document] Recovery heuristics are dotted-name-tuned and only the JVM pair is validated. language="multi" will happily merge Go/TS with split(".")-based heuristics and zero test coverage. Worth documenting supported pairs for the MVP.

Nits: resolve_name's leaf fallback contains dead code (leaf is always simple_name after the dotted-name guard); _LANG_PREFERRED_ROOTS has a "scala" entry with no Scala parser; the single-language parse() branch passes provided files unfiltered while the multi branch filters via _files_for_language; _resolve_languages for "multi" counts directories from rglob("*"); explicit languages=["kotlin","java"] vs ["java","kotlin"] changes collision winners — sort internally or document.

Two things outside this PR: found a pre-existing java parser bug while testing (single-segment package auth; is silently dropped — _extract_package only accepts scoped_identifier; worth its own issue), and on land order — this should land before #18 (Rust); your ingest.py rewrite is structural and #18's touches replay trivially on top, not the reverse.

Full suite on the branch: 294 passed, 10 skipped. Scope matches the roadmap MVP honestly (IDL bridges deferred, docs updated). Fix finding 1 (family-scoping likely closes 1+2 together) and this lands.

tuannx commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator Author

Proposal: minimal safe-union architecture for polyglot analysis

The review findings suggest that extending the current global FQN/leaf heuristics would make multi-language support less trustworthy as more parsers are added. I propose narrowing this PR to a safe union model and evolving cross-language links incrementally.

Required behavior

  • Parse every requested supported language into its own subgraph.
  • Preserve all source entities; a same-FQN declaration in another language must not overwrite it.
  • Add cross-language edges only through a compatible language-group resolver or an explicit API/message contract.
  • Keep unsupported language pairs disconnected and report them instead of guessing or failing the whole analysis.
  • Architecture recovery uses evidence-backed edges by default.

Minimal new components

  1. LanguageGroup

    • Declares compatible semantic/runtime families.
    • Initial group: jvm = {java, kotlin}.
    • Likely follow-ups: javascript = {javascript, typescript}, dotnet, apple, and explicit native/FFI groups.
    • Languages without a compatible resolver remain isolated.
  2. PolyglotGraph

    • Wraps existing per-language DependencyGraph values instead of flattening them immediately.
    • Carries graphs_by_language, cross-language edges, and diagnostics.
    • A source entity is addressed by at least (language, fqn) at the union boundary, preventing silent collision loss without requiring every parser to be rewritten first.
  3. CrossLanguageResolver

  4. ContractGraph / ContractAdapter

    • Represents language-neutral API and message contracts: services, operations, messages, fields, and events.
    • Source entities bind to contract symbols through evidence such as generated-code metadata, annotations, routes, or build configuration.
    • The first concrete adapters should be Protobuf/gRPC and OpenAPI; defining the port now is useful, but both adapters do not need to block this PR.
  5. LinkReport

    • Exposes linked_groups, unsupported_pairs, collisions, unresolved_references, and edge provenance.
    • MCP consumers should see these diagnostics; log-only collision warnings are insufficient.

Data flow

ingest
  -> parse each language independently
  -> PolyglotGraph
  -> language-group + contract resolvers
  -> trusted graph view
  -> recover / metrics / MCP consumers

Scope proposed for PR #19

  • Safe union of all selected languages.
  • JVM-only relinking for Java↔Kotlin.
  • No fabricated links between JVM and Python/Go/TypeScript/etc.
  • Deterministic validation, deduplication, ordering, and cache behavior for languages=[...].
  • Preserve/report collisions rather than keeping the first entity silently.
  • Document the supported relinking group honestly.
  • Keep the existing ingest -> parse -> recover MCP workflow backward compatible.

Acceptance regressions

  • Java↔Kotlin imports/inheritance still relink correctly.
  • Python PaymentHandler(Base) must not link to an unrelated Java Base.
  • A Python import must not bind to a coincidentally matching Java FQN.
  • Cross-language FQN collisions preserve both declarations or produce a structured non-lossy diagnostic.
  • Reversing the input language order produces identical graph/cache behavior.
  • MCP E2E reports linked and unsupported groups.

Incremental rollout

  1. PR feat: polyglot multilang parse (roadmap #18 MVP) #19: safe union + JvmResolver + diagnostics.
  2. Collision-safe semantic identity and resolver registry refinement.
  3. Contract model + Protobuf/gRPC adapter.
  4. OpenAPI adapter and framework bindings.
  5. Additional language groups and FFI/IDL resolvers, each with real-repository and negative regression coverage.

This keeps the current parser, graph, cache, recovery, and MCP investments, while replacing one global heuristic with small explicit contracts. Does this direction match the intended landing scope for #19?

@lemduc

lemduc commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Thanks for the very precise review — all findings addressed in 33be0d6.

1. [Blocking] merge_and_relink is language-blind — fixed

Added language families to multilang.py: jvm = {java, kotlin}, every other language is its own family. language_family() is now the gate on all resolution paths, not just the three named:

  • resolve_name(): exact-FQN, alias, import-suffix, same-package and the unique-leaf fallback each require the target entity's family to match the source's.
  • relink_edges(): the exact-FQN import match now checks the target entity's family; the elif "." not in imp leaf fallback resolves through a family-scoped index.
  • _build_fqn_index() is now family -> {leaf: fqn}, with uniqueness evaluated inside the family — so a Java Base and a Python Base neither shadow nor resolve to each other, while in-family resolution keeps working.

I reproduced both fabrications on the pre-fix code with a new committed fixture (tests/fixtures/python_java_mixed/, Python + Java, incl. class PaymentHandler(Base) and import com.auth.service):

OLD: ('app.service.PaymentHandler', 'com.example.core.Base', 'extends')
     ('app.service.PaymentHandler', 'com.auth.service', 'import')
     ('app.service.PaymentHandler.login', 'com.auth.service', 'import')
NEW: []   (entities 6 -> 7, see finding 2)

New tests in tests/test_parsers/test_multilang_family_scoping.py (16 tests) cover each path negatively (python→java extends via unique leaf, leaf import, exact-FQN import, same-package) and positively for the JVM pair, plus an end-to-end parse(languages=["java","python"]) assertion that no edge connects two different families.

2. [Should fix] FQN collisions silently drop entities — fixed, no data loss

Cross-family collisions no longer keep-first: the later entity is re-keyed as <fqn>#<language> and that graph's own edges and package listings are remapped, so both survive (com.auth.service.login java + com.auth.service.login#python). Within a family the previous keep-first behaviour is retained (Java before Kotlin), as your existing test expects.

Counts are now agent-visible, not log-only: new DependencyGraph.metadata carries fqn_collisions, fqn_collisions_same_family, fqn_collisions_cross_family and fqn_collision_details. It is serialized only when non-empty and is surfaced in MCP parse summaries.

3. [Document] Supported pairs — documented

jvm (java+kotlin) is the only family with cross-language relinking and the only validated pair; documented in the multilang module docstring, parse() and the MCP parse/server docstrings, a README table, and ROADMAP #18. language="multi" on e.g. Go+TS now parses and merges but provably never links across families.

Nits

  • dead code: dropped the leaf = simple_name.split(".")[-1] after the dotted-name guard
  • removed the "scala" entry from _LANG_PREFERRED_ROOTS (no Scala parser; the generic src/main/scala probe in _SOURCE_ROOTS stays)
  • provided-files filtering: both branches now go through _files_for_language, with a warning naming the skipped files
  • _resolve_languages for "multi" now counts files only (if f.is_file()), not directories
  • explicit languages=[...] is sorted + de-duplicated internally, so ["kotlin","java"] and ["java","kotlin"] give identical graphs (asserted by a test)

Determinism invariant

Single-language output is byte-identical to origin/main: I diffed graph_to_dict(parse(..., language=L)) for six fixture/language combinations (java, python, kotlin ×2, c) between origin/main's src/ and this branch — no differences. metadata is omitted from serialization when empty, so single-language JSON/baselines are unchanged. Two tests lock this in (test_single_language_parse_carries_no_metadata, test_single_language_parse_matches_direct_parser_output).

Separate pre-existing bug

Filed as #27 (java parser drops single-segment package auth;_extract_package only accepts scoped_identifier), with repro and suggested fix. Not touched in this PR.

Verification

  • pytest: 320 passed, 0 skipped (baseline on this branch before the change: 304 passed, with [dev,languages,mcp] extras installed)
  • ruff check src/ tests/: clean
  • mypy src/: 220 errors, exactly the pre-change baseline (all pre-existing; no new ones in the touched files)

tuannx and others added 5 commits July 22, 2026 15:43
Add languages=[] / language="multi" for Maven dual-root discovery and
merge+relink of import/extends/implements across Java↔Kotlin FQN space
(roadmap #18 MVP). Wire MCP and CI analyze inputs; keep first entity on
cross-language FQN collisions.

Co-authored-by: Cursor <cursoragent@cursor.com>
Cover ingest→parse→recover, MCP session pipeline, and CLI
--languages self-analysis against existing mixed fixtures.

Co-authored-by: Cursor <cursoragent@cursor.com>
Detect multi-language parse from path suffixes without requiring files
on disk, and fail fast on unknown ingest language overrides.

Co-authored-by: Cursor <cursoragent@cursor.com>
The merge+relink introduced for roadmap #18 was language-blind: all three
resolution paths (unique-leaf fallback in resolve_name, leaf import fallback
and exact-FQN import match in relink_edges) could bind an entity to one written
in an unrelated language. On a Python+Java fixture this fabricated
`app.service.PaymentHandler -[extends]-> com.example.core.Base` and an import
edge from a Python module to the Java class with the same FQN, which then
propagated into pkg recovery and WCA clustering.

Introduce language families (jvm = java+kotlin; every other language its own
family) and gate every resolution path plus the merge step on family
compatibility. The unique-leaf index is now built per family, so in-family
resolution is unaffected. Cross-family FQN collisions no longer drop an entity
silently: the later one is re-keyed as `<fqn>#<language>` with its edges and
package listings remapped, and collision counts are reported in the new
DependencyGraph.metadata (also surfaced in MCP summaries, since agents never
see log lines).

Single-language output is unchanged byte-for-byte (verified against
origin/main across six fixture/language combinations; metadata is omitted from
serialization when empty).

Also from the review:
- document the supported polyglot pairs (module docstring, parse/MCP
  docstrings, README table, ROADMAP)
- drop the dead leaf-split in resolve_name
- remove the scala entry from _LANG_PREFERRED_ROOTS (no Scala parser)
- filter provided files by language in the single-language branch too, with a
  warning for skipped files
- count only files (not directories) when resolving language="multi"
- sort/deduplicate an explicit languages list so ordering cannot change
  collision winners

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lemduc
lemduc force-pushed the feat/polyglot-multilang-parse branch from 33be0d6 to 590caa2 Compare July 22, 2026 08:43
@lemduc
lemduc merged commit 2f45760 into main Jul 24, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants