feat: polyglot multilang parse (roadmap #18 MVP)#19
Conversation
🤖 Architecture Analysis SummaryPowered by arcade-agent — automatic architectural self-analysis 📈 Metric EvolutionBaseline commit: Legend: 🟢 better · 🔴 worse · 🟡 low impact · ⚪ no change
🏛️ Current Architecture
🧭 Principle Signals
🎯 Score DriversBiggest risks
Strongest areas
🕸️ High-Level Designgraph 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
🏗️ Components breakdown
🚨 Architectural Smells
📈 Evolution vs BaselineBaseline commit: Architecture-to-Architecture (A2A) Comparison
Component matching detailsMatched:
High-level component statistics
Before/After Mermaid diagramsBaseline 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
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
Component dependency delta
💡 CI/CD Insights
📄 View HTML reports and artifacts This comment is auto-generated by the self-dogfooding CI job. It updates on every push to this PR. |
Architecture Drift ReportAlgorithm: PKG | Entities: 738 | Components: 7 Drift from Baseline
Changes
Smells (1)
Generated by arcade-agent |
There was a problem hiding this comment.
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
DependencyGraphmerge + edge relinking (merge_and_relink,relink_edges) to support polyglot dependency recovery. - Extend
ingest/parseAPIs (and MCP adapter + CI action/workflow + self-analysis runner) with alanguagesinput 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.
Copilot review triage — fixed in
|
|
Follow-up review completed in
Validation: |
lemduc
left a comment
There was a problem hiding this comment.
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 localBase→app.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.
Proposal: minimal safe-union architecture for polyglot analysisThe 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
Minimal new components
Data flowScope proposed for PR #19
Acceptance regressions
Incremental rollout
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? |
|
Thanks for the very precise review — all findings addressed in 33be0d6. 1. [Blocking]
|
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>
33be0d6 to
590caa2
Compare
Summary
languages=[...]orlanguage="multi": discovers Maven dual roots (src/main/java+src/main/kotlin), merges per-language graphs, and relinksimport/extends/implementsacross the combined FQN space (Java↔Kotlin first; same mechanism for other same-FQN / import-linked pairs).ingest/parseand CI analyze action / reusable workflow with alanguagesinput (comma-separated); single-language callers remain backward compatible.DependencyGraph.mergekept the second).--languagesself-analysis on Java+Kotlin fixtures.Test plan
ruff checkon touched multilang sources/testspytest 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)languages: java,kotlinon a mixed Maven repomainand were not regressively hit by these changes (parser suite green)Notes
feature/multilang-parse(meeting notes,uv.lock, patches, etc.) intentionally excluded.fqn_indexfallback can mis-resolve ambiguous short names across packages; no gRPC/OpenAPI edge bridging yet.Made with Cursor