diff --git a/services/api-server/src/api_server/ai_structure_service.py b/services/api-server/src/api_server/ai_structure_service.py index 6270a46..d9213ab 100644 --- a/services/api-server/src/api_server/ai_structure_service.py +++ b/services/api-server/src/api_server/ai_structure_service.py @@ -162,8 +162,9 @@ def _write_codex_package( _write_json(root / "indexes" / "source-map.json", {"files": source_map}) if snapshot is not None: (root / "raw" / "sir_snapshot.json").write_bytes(snapshot_to_json(snapshot)) - objects = _ai_objects(snapshot) - modules = _ai_modules(snapshot) + source_lookup = _source_lookup(source_map) + objects = _ai_objects(snapshot, source_lookup) + modules = _ai_modules(snapshot, source_lookup) _write_json(root / "indexes" / "objects.json", objects) _write_json(root / "indexes" / "modules.json", modules) _write_json(root / "indexes" / "edges.json", [edge.model_dump(mode="json") for edge in snapshot.edges]) @@ -215,6 +216,24 @@ def _copy_codex_sources(input_path: Path, target: Path) -> list[dict[str, Any]]: return copied +def _source_lookup(source_map: list[dict[str, Any]]) -> dict[str, str]: + lookup: dict[str, str] = {} + for item in source_map: + if not item.get("copied"): + continue + codex_path = str(item.get("codex_path") or "") + if not codex_path: + continue + for key in ["relative_path", "original_path"]: + value = str(item.get(key) or "") + if not value: + continue + normalized = value.replace("\\", "/") + lookup[normalized] = codex_path + lookup[str(Path(value)).replace("\\", "/")] = codex_path + return lookup + + def _codex_agents_markdown(manifest: dict[str, Any]) -> str: return f"""# AGENTS.md for 1C context package @@ -371,6 +390,9 @@ def _module_markdown(item: dict[str, Any]) -> str: def _local_source_path(item: dict[str, Any]) -> str: + local = str(item.get("local_source_path") or "") + if local: + return local source = item.get("source") or {} if not isinstance(source, dict): return "" @@ -378,6 +400,16 @@ def _local_source_path(item: dict[str, Any]) -> str: return f"source/{source_path}" if source_path else "" +def _source_ref_local_path(source_ref: object | None, source_lookup: dict[str, str]) -> str: + if source_ref is None: + return "" + source_path = str(getattr(source_ref, "source_path", "") or "") + if not source_path: + return "" + normalized = source_path.replace("\\", "/") + return source_lookup.get(normalized) or source_lookup.get(str(Path(source_path)).replace("\\", "/")) or "" + + def _normalized_tree_markdown(normalized: NormalizedProject) -> str: lines = [f"# Metadata Tree: {normalized.project_id or 'project'}", ""] for group in normalized.configuration.groups: @@ -395,7 +427,7 @@ def _normalized_tree_markdown(normalized: NormalizedProject) -> str: return "\n".join(lines) -def _ai_objects(snapshot: SirSnapshot) -> list[dict[str, Any]]: +def _ai_objects(snapshot: SirSnapshot, source_lookup: dict[str, str] | None = None) -> list[dict[str, Any]]: return [ { "kind": node.kind.value if hasattr(node.kind, "value") else str(node.kind), @@ -404,6 +436,7 @@ def _ai_objects(snapshot: SirSnapshot) -> list[dict[str, Any]]: "lineage_id": node.lineage_id, "semantic_id": node.semantic_id, "source": None if node.source_ref is None else node.source_ref.model_dump(mode="json"), + "local_source_path": _source_ref_local_path(node.source_ref, source_lookup or {}), "attributes": node.attributes, } for node in snapshot.nodes @@ -411,13 +444,14 @@ def _ai_objects(snapshot: SirSnapshot) -> list[dict[str, Any]]: ] -def _ai_modules(snapshot: SirSnapshot) -> list[dict[str, Any]]: +def _ai_modules(snapshot: SirSnapshot, source_lookup: dict[str, str] | None = None) -> list[dict[str, Any]]: return [ { "name": node.name, "qualified_name": node.qualified_name, "lineage_id": node.lineage_id, "source": None if node.source_ref is None else node.source_ref.model_dump(mode="json"), + "local_source_path": _source_ref_local_path(node.source_ref, source_lookup or {}), "attributes": node.attributes, } for node in snapshot.nodes diff --git a/services/api-server/tests/test_api.py b/services/api-server/tests/test_api.py index 0d19021..55e009e 100644 --- a/services/api-server/tests/test_api.py +++ b/services/api-server/tests/test_api.py @@ -1,4 +1,5 @@ from pathlib import Path +import json import re import time from types import SimpleNamespace @@ -1716,6 +1717,10 @@ def test_ai_structure_prepare_writes_ai_ready_package(tmp_path: Path): assert (codex_package / "raw" / "normalized_project.json").exists() assert (codex_package / "source" / "metadata.xml").exists() assert (codex_package / "source" / "Интеграция.bsl").read_text(encoding="utf-8").startswith("Процедура") + modules_index = json.loads((codex_package / "indexes" / "modules.json").read_text(encoding="utf-8")) + assert any(item.get("local_source_path") == "source/Интеграция.bsl" for item in modules_index) + module_docs = "\n".join(path.read_text(encoding="utf-8") for path in (codex_package / "modules").glob("*.md")) + assert "Local source: `source/Интеграция.bsl`" in module_docs assert "generated by SFERA for Codex" in (codex_package / "AGENTS.md").read_text(encoding="utf-8") assert "Use `source/`" in (codex_package / "AGENTS.md").read_text(encoding="utf-8") assert "Copy this whole folder into the Codex project" in (codex_package / "README.md").read_text(encoding="utf-8")