Skip to content

Commit 2265cf2

Browse files
HuYaSenQingtaoLi1
andauthored
feat: multi-language support for the encoder and decoder pipelines (#67)
### Summary This PR extends CoderMind's **encoder** (repository → RPG graph) and **decoder** (docs → repository) pipelines from Python-only to **multi-language**, adding support for Python, JavaScript, TypeScript, Go, C, C++, and Rust. All language differences are isolated behind two abstraction layers: - **`lang_parser`** — a tree-sitter–based parser module giving the encoder uniform code-structure and dependency extraction. - **`decoder_lang` backends** — one backend per language encapsulating file layout, entry points, test commands, code structure, and build environment; every decoder stage (plan / codegen / verification) goes through these backends. ### Encoder - New standalone `lang_parser` module (tree-sitter backend + 7 language parsers/configs) wired into the RPG / dep-graph / encoder pipeline. - Language metadata flows through `refactor_tree` `NodeMetaData`; `rpg.json`'s `meta.language` reflects the real repo instead of always `python`. - Dominant-language detection; `add_file` and semantic re-runs dispatch per language; non-Python class-like units normalized. - `rpg.json` single source of truth (dep_graph embedded); atomic writes eliminate half-written files. - C/C++ calls resolve to the definition, not the header prototype; scan exclusions centralized and per-commit LLM exclusion dropped (perf). ### Decoder - Seven language backends with a unified protocol: entry points, test commands, inheritance, code structure, build env, unit-kind. - **Planning**: language metadata flows through feature spec → build → tree → skeleton → interfaces → tasks; interface design/review works for every language with language-neutral prompts; non-Python files ordered by real imports, not Python AST. - **Code generation**: codegen / post-verify / global-review / subtree-review / final-test all route through the backend and use native test commands (go test, npm, cargo, ctest, …); non-Python projects no longer emit Python files or run venv/pip setup. - **Verification**: on-disk source fallback for language resolution; zero-test "no-op pass" rejected; bounded repair loop in final-test; unified git branch-name sanitization. - **Entry coordination**: `<MAIN_ENTRY>` and the skeleton entry are reconciled across all 7 languages to avoid a duplicate `main`. - **Interface review** (final fix): unit-level and feature-level orphan checks unified into a single shared predicate, removing spurious WARNs on framework roots/factories/callbacks; entry-point name matching normalized (`RunMain` ↔ `function RunMain`). --------- Co-authored-by: Qingtao Li <qingtaoli@microsoft.com>
1 parent aeb4f51 commit 2265cf2

137 files changed

Lines changed: 14749 additions & 1844 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CoderMind/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ Reverse Direction: Code → RPG │
6969
│ │ │ (full) │ │ (manual │ │
7070
└──────────────────┘ └────┬─────┘ │ fallback)│ │
7171
rpg.json └──────────┘ │
72-
dep_graph.json rpg.json / dep_graph.json
72+
(includes dep_graph) rpg.json
7373
│ │
7474
└──────────────────────────────────────────┘
7575

CoderMind/pyproject.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ dependencies = [
1414
"pytest",
1515
"tree-sitter",
1616
"tree-sitter-json",
17+
# Tree-sitter grammars for the lang_parser module (Go / TS / JS / C / C++ /
18+
# Rust). Installed by default so every language works out of the box; each
19+
# is lazy-loaded in lang_parser/tree_sitter_backend.py, so if one grammar
20+
# fails to import on a given platform only that language degrades.
21+
"tree-sitter-go>=0.23.4",
22+
"tree-sitter-typescript>=0.23.2",
23+
"tree-sitter-javascript>=0.23.1",
24+
"tree-sitter-c>=0.24.2",
25+
"tree-sitter-cpp>=0.23.4",
26+
"tree-sitter-rust>=0.24.2",
1727
"networkx",
1828
"rank_bm25",
1929
"rapidfuzz",

CoderMind/scripts/build_data_flow.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
# Import centralized paths
3939
from common.paths import SKELETON_FILE, DATA_FLOW_FILE, REPO_RPG_FILE
4040
from common import get_project_background_context
41+
from common.language_meta import extract_language_metadata, metadata_with_languages
4142

4243

4344
# ============================================================================
@@ -139,6 +140,7 @@ def build(self, skeleton: Dict[str, Any]) -> Dict[str, Any]:
139140
"""
140141
# Get repository info
141142
repo_name, repo_info = get_repo_info_from_files()
143+
primary_language, _ = extract_language_metadata(skeleton)
142144

143145
# Enrich repo_info with project background / technology context
144146
project_background = get_project_background_context()
@@ -185,7 +187,8 @@ def build(self, skeleton: Dict[str, Any]) -> Dict[str, Any]:
185187
max_iterations=self.max_iterations,
186188
logger=self.logger,
187189
trajectory=self.trajectory,
188-
step_id=self._current_step_id
190+
step_id=self._current_step_id,
191+
target_language=primary_language,
189192
)
190193

191194
result = agent.build_data_flow(
@@ -198,6 +201,7 @@ def build(self, skeleton: Dict[str, Any]) -> Dict[str, Any]:
198201

199202
# Add components to result
200203
result["components"] = functional_areas
204+
result["meta"] = metadata_with_languages(skeleton)
201205

202206
# Update trajectory
203207
if self.trajectory and self._current_step_id:

CoderMind/scripts/build_skeleton.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
REPO_RPG_FILE,
3434
)
3535
from common import print_unicode_table
36+
from common.language_meta import extract_language_metadata, metadata_with_languages
3637
from pathlib import Path as PPath
3738
from rpg import NodeMetaData
3839
from skeleton.skeleton_prompts import extract_features_from_subtree
@@ -74,6 +75,12 @@ def convert_node(node):
7475
output = {
7576
"repository_name": rpg.repo_name,
7677
"repository_purpose": rpg.repo_info,
78+
"meta": metadata_with_languages({
79+
"meta": {
80+
"primary_language": getattr(rpg.repo_node.meta, "language", None)
81+
if rpg.repo_node and rpg.repo_node.meta else None
82+
}
83+
}),
7784
"root": convert_node(skeleton.root),
7885
"statistics": {
7986
"total_components": len([n for n in rpg.nodes.values() if n.level == 1]),
@@ -100,6 +107,7 @@ def __init__(self, max_iterations: int = 10, trajectory: Trajectory = None):
100107

101108
# Build state
102109
self.repo_name = ""
110+
self.target_language = None
103111
self.repo_data = {}
104112
self.rpg = None
105113
self.skeleton = None
@@ -121,6 +129,7 @@ def build(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
121129
"""Execute complete skeleton building workflow."""
122130
self.repo_data = input_data
123131
self.repo_name = input_data.get("repository_name", "project")
132+
self.target_language = extract_language_metadata(input_data)[0]
124133
components = input_data.get("components", [])
125134

126135
if not components:
@@ -242,7 +251,8 @@ def _step2_file_design(self) -> bool:
242251
rpg=self.rpg,
243252
max_iterations=self.max_iterations,
244253
trajectory=self.trajectory,
245-
step_id=self._current_step_id
254+
step_id=self._current_step_id,
255+
target_language=self.target_language,
246256
)
247257

248258
# Run file design process
@@ -281,6 +291,12 @@ def _build_result(self) -> Dict[str, Any]:
281291
"""Build the final result dictionary in CoderMind format."""
282292
# Convert to CoderMind compatible format
283293
result = convert_skeleton_to_cmind_format(self.skeleton, self.rpg)
294+
result["meta"] = metadata_with_languages({
295+
"meta": {
296+
"primary_language": self.target_language,
297+
"target_languages": [self.target_language] if self.target_language else [],
298+
}
299+
})
284300

285301
# Add statistics
286302
result["statistics"].update({
@@ -553,7 +569,7 @@ def patch_missing(input_data: Dict[str, Any]) -> Dict[str, Any]:
553569
json.dump(result, f, indent=2, ensure_ascii=False)
554570
rpg.save_json(str(REPO_RPG_FILE), indent=2)
555571

556-
print(f"\n[OK] Patch complete:")
572+
print("\n[OK] Patch complete:")
557573
print(f" - Missing features patched: {total_missing}")
558574
print(f" - New files created: {new_file_count}")
559575
print(f" - Features merged into existing files: {merged_count}")

CoderMind/scripts/check_base_classes.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
#!/usr/bin/env python3
22
"""Check Base Classes Script.
33
4-
Function: Validate base_classes.json state and validate Python syntax
4+
Function: Validate base_classes.json state and target-language syntax
55
- Checks if base_classes.json exists (init state)
66
- Validates JSON structure (error state if invalid)
7-
- Validates Python code syntax (error state if syntax errors)
7+
- Validates source syntax (error state if syntax errors)
88
- Returns update state if valid
99
1010
Input: .cmind/base_classes.json
@@ -15,8 +15,9 @@
1515
from pathlib import Path
1616
from typing import Dict, Any, List, Tuple
1717

18-
# Import from common utils
19-
from common import validate_python_syntax, extract_class_names
18+
from common.language_meta import extract_language_metadata
19+
from decoder_lang import get_backend
20+
from func_design.base_class_agent import extract_declaration_names
2021

2122
# Import centralized paths
2223
from common.paths import BASE_CLASSES_FILE
@@ -34,6 +35,7 @@ def load_json(file_path: Path) -> Dict[str, Any]:
3435
def validate_base_classes_structure(data: Dict[str, Any]) -> Tuple[bool, List[str]]:
3536
"""Validate base classes structure."""
3637
errors = []
38+
backend = get_backend(extract_language_metadata(data)[0])
3739

3840
base_classes = data.get("base_classes", [])
3941

@@ -53,16 +55,15 @@ def validate_base_classes_structure(data: Dict[str, Any]) -> Tuple[bool, List[st
5355
elif not bc[field]:
5456
errors.append(f"Base class {i}: field '{field}' is empty")
5557

56-
# Validate Python syntax
5758
code = bc.get("code", "")
5859
if code:
59-
is_valid, error = validate_python_syntax(code)
60+
is_valid, error = backend.syntax_check(code, bc.get("file_path", ""))
6061
if not is_valid:
6162
# Try to get name from bc or extract from code
6263
name = bc.get("name", "")
6364
if not name:
64-
class_names = extract_class_names(code)
65-
name = class_names[0] if class_names else "unknown"
65+
declarations = extract_declaration_names(code, backend)
66+
name = declarations[0] if declarations else "unknown"
6667
errors.append(f"Base class {i} ({name}): syntax error - {error}")
6768

6869
# Also validate data_structures if present
@@ -94,11 +95,13 @@ def validate_base_classes_structure(data: Dict[str, Any]) -> Tuple[bool, List[st
9495

9596
code = ds.get("code", "")
9697
if code:
97-
is_valid, error = validate_python_syntax(code)
98+
is_valid, error = backend.syntax_check(
99+
code,
100+
ds.get("file_path", f"data_structure{backend.file_extension}"),
101+
)
98102
if not is_valid:
99-
name = ""
100-
class_names = extract_class_names(code)
101-
name = class_names[0] if class_names else "unknown"
103+
declarations = extract_declaration_names(code, backend)
104+
name = declarations[0] if declarations else "unknown"
102105
errors.append(f"Data structure {i} ({name}): syntax error - {error}")
103106

104107
return len(errors) == 0, errors
@@ -172,6 +175,7 @@ def inspect_state(base_classes_path: Path) -> Dict[str, Any]:
172175
"data_structure_names": ds_class_names,
173176
"data_structure_subtrees": ds_subtrees,
174177
"data_structure_file_paths": ds_file_paths,
178+
"language": extract_language_metadata(data)[0],
175179
}
176180
}
177181

CoderMind/scripts/check_code_gen.py

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -362,16 +362,53 @@ def determine_state(
362362
# actually generating the expected files.
363363
missing_artifacts = []
364364
repo_root = REPO_DIR
365-
366-
# Check for main_entry task artifact
365+
366+
# Resolve the target language so entry-point / dependency artifact
367+
# checks are not hard-coded to Python's ``main.py`` /
368+
# ``requirements.txt``. Routes through the canonical repo resolver so
369+
# the language is inferred from the real on-disk sources when the rpg
370+
# metadata is missing, rather than silently assuming Python. Falls
371+
# back to Python on any failure so the check degrades to its previous
372+
# behaviour rather than crashing.
373+
backend = None
374+
try:
375+
from common.paths import REPO_RPG_FILE
376+
from decoder_lang import resolve_repo_backend
377+
rpg_obj = None
378+
if Path(REPO_RPG_FILE).is_file():
379+
rpg_obj = json.loads(Path(REPO_RPG_FILE).read_text(encoding="utf-8"))
380+
backend = resolve_repo_backend(repo_root, rpg_obj=rpg_obj)
381+
except Exception: # noqa: BLE001 — degraded mode: assume Python
382+
backend = None
383+
384+
# Check for main_entry task artifact (language-aware entry path).
367385
main_entry_ids = [tid for tid in completed_ids if tid.startswith("<MAIN_ENTRY>")]
368-
if main_entry_ids and not (repo_root / "main.py").exists():
369-
missing_artifacts.append("main.py (from <MAIN_ENTRY> task)")
370-
371-
# Check for requirements task artifact
386+
if main_entry_ids:
387+
if backend is not None:
388+
# Accept any of the backend's entry-point shapes. A single
389+
# canonical path is too strict when the skeleton placed the
390+
# entry off-canonical (e.g. C++ ``src/cli/main.cpp``) or the
391+
# language uses a glob convention (Go ``cmd/*/main.go``).
392+
candidates = backend.entry_point_candidates()
393+
entry_exists = any(
394+
(any(repo_root.glob(c)) if "*" in c else (repo_root / c).exists())
395+
for c in candidates
396+
)
397+
if not entry_exists:
398+
missing_artifacts.append(
399+
f"{candidates[0]} (from <MAIN_ENTRY> task)"
400+
)
401+
elif not (repo_root / "main.py").exists():
402+
missing_artifacts.append("main.py (from <MAIN_ENTRY> task)")
403+
404+
# Check for requirements task artifact. The dependency-manifest
405+
# filename is language-specific; only Python's is asserted here
406+
# (other languages manage deps via go.mod / Cargo.toml / package.json
407+
# which the dependency task and build steps validate separately).
372408
req_ids = [tid for tid in completed_ids if tid.startswith("<REQUIREMENTS>")]
373-
if req_ids and not (repo_root / "requirements.txt").exists():
374-
missing_artifacts.append("requirements.txt (from <REQUIREMENTS> task)")
409+
if req_ids and (backend is None or backend.name == "python"):
410+
if not (repo_root / "requirements.txt").exists():
411+
missing_artifacts.append("requirements.txt (from <REQUIREMENTS> task)")
375412

376413
if missing_artifacts:
377414
result["type"] = "incomplete"

CoderMind/scripts/check_tasks.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,10 @@ def check_state(input_path: Path, output_path: Path) -> Dict[str, Any]:
338338
if not cross_validation["is_consistent"]:
339339
warning_count = len(cross_validation["warnings"])
340340
result["type"] = "warning"
341-
result["message"] = f"tasks.json exists but has {warning_count} unit mismatches with interfaces."
341+
result["message"] = (
342+
f"tasks.json exists but has {warning_count} unit mismatches with interfaces. "
343+
"This is a cross-stage contract violation; plan.py will rebuild tasks and downstream stages."
344+
)
342345
else:
343346
result["type"] = "update"
344347
result["message"] = f"Valid tasks.json exists with {stats['total_tasks']} tasks and {stats['total_units']} units."

0 commit comments

Comments
 (0)