Add source preview to AI structure package
CI / python (push) Has been cancelled
CI / rust (push) Has been cancelled

This commit is contained in:
2026-05-22 15:43:24 +03:00
parent 7501111d29
commit 0a9ab94679
4 changed files with 46 additions and 0 deletions
@@ -62,9 +62,11 @@ def prepare_ai_structure(
diagnostics, diagnostics,
binaries, binaries,
_source_layout_summary(input_path), _source_layout_summary(input_path),
_source_preview_summary(input_path),
) )
_write_json(output_path / "manifest.json", manifest) _write_json(output_path / "manifest.json", manifest)
_write_json(output_path / "source_inventory.json", {"files": files}) _write_json(output_path / "source_inventory.json", {"files": files})
_write_json(output_path / "source_preview.json", manifest.get("source_preview") or [])
if snapshot is not None: if snapshot is not None:
(output_path / "sir_snapshot.json").write_bytes(snapshot_to_json(snapshot)) (output_path / "sir_snapshot.json").write_bytes(snapshot_to_json(snapshot))
_write_json(output_path / "ai_objects.json", _ai_objects(snapshot)) _write_json(output_path / "ai_objects.json", _ai_objects(snapshot))
@@ -104,6 +106,7 @@ def _manifest(
diagnostics: list[str], diagnostics: list[str],
binaries: list[dict[str, Any]], binaries: list[dict[str, Any]],
source_layout: dict[str, Any], source_layout: dict[str, Any],
source_preview: list[dict[str, Any]],
) -> dict[str, Any]: ) -> dict[str, Any]:
return { return {
"version": AI_STRUCTURE_VERSION, "version": AI_STRUCTURE_VERSION,
@@ -116,6 +119,7 @@ def _manifest(
"files_count": len(files), "files_count": len(files),
"binary_1c_files": binaries, "binary_1c_files": binaries,
"source_layout": source_layout, "source_layout": source_layout,
"source_preview": source_preview,
"artifacts": _artifacts(snapshot, normalized), "artifacts": _artifacts(snapshot, normalized),
"snapshot": None "snapshot": None
if snapshot is None if snapshot is None
@@ -148,6 +152,7 @@ def _artifacts(snapshot: SirSnapshot | None, normalized: NormalizedProject | Non
"export_plan.md", "export_plan.md",
"codex_package", "codex_package",
"project_layout.json", "project_layout.json",
"source_preview.json",
"compact_objects.json", "compact_objects.json",
"compact_modules.json", "compact_modules.json",
] ]
@@ -201,6 +206,7 @@ def _write_codex_package(
_write_json(root / "indexes" / "source-inventory.json", {"files": files}) _write_json(root / "indexes" / "source-inventory.json", {"files": files})
_write_json(root / "indexes" / "source-map.json", {"files": source_map}) _write_json(root / "indexes" / "source-map.json", {"files": source_map})
_write_json(root / "indexes" / "project-layout.json", manifest.get("source_layout") or {}) _write_json(root / "indexes" / "project-layout.json", manifest.get("source_layout") or {})
_write_json(root / "indexes" / "source-preview.json", manifest.get("source_preview") or [])
_write_json(root / "indexes" / "objects-compact.json", compact_objects) _write_json(root / "indexes" / "objects-compact.json", compact_objects)
_write_json(root / "indexes" / "modules-compact.json", compact_modules) _write_json(root / "indexes" / "modules-compact.json", compact_modules)
_write_json(root / "compact" / "objects.json", compact_objects) _write_json(root / "compact" / "objects.json", compact_objects)
@@ -592,6 +598,29 @@ def _source_layout_summary(root: Path) -> dict[str, Any]:
} }
def _source_preview_summary(root: Path) -> list[dict[str, Any]]:
files = [root] if root.is_file() else sorted(path for path in root.rglob("*") if path.is_file())
object_files = _preview_relative_paths(
root,
sorted(
[path for path in files if path.suffix.casefold() in {".xml", ".mdo"}],
key=lambda path: (
0 if any(part.casefold() in {"configuration", "конфигурация"} for part in path.parts) else 1,
str(path).casefold(),
),
),
limit=6,
)
module_files = _preview_relative_paths(root, [path for path in files if path.suffix.casefold() == ".bsl"], limit=6)
layout = _source_layout_summary(root)
return [
{"label": "Главная конфигурация", "items": [layout["main_configuration_root"]]},
{"label": "Папки расширений", "items": layout["extension_roots"] or ["нет"]},
{"label": "Первые файлы объектов", "items": object_files or ["не найдены"]},
{"label": "Первые файлы модулей", "items": module_files or ["не найдены"]},
]
def _compact_objects(normalized: NormalizedProject | None) -> list[dict[str, Any]]: def _compact_objects(normalized: NormalizedProject | None) -> list[dict[str, Any]]:
if normalized is None: if normalized is None:
return [] return []
@@ -679,6 +708,16 @@ def _project_brief_markdown(manifest: dict[str, Any], compact_objects: list[dict
return "\n".join(lines) + "\n" return "\n".join(lines) + "\n"
def _preview_relative_paths(root: Path, files: list[Path], *, limit: int) -> list[str]:
preview: list[str] = []
for path in files[:limit]:
if root.is_file():
preview.append(path.name)
else:
preview.append(path.relative_to(root).as_posix())
return preview
def _export_plan_markdown(project_id: str, input_path: Path, output_path: Path, binaries: list[dict[str, Any]], parseable: bool) -> str: def _export_plan_markdown(project_id: str, input_path: Path, output_path: Path, binaries: list[dict[str, Any]], parseable: bool) -> str:
lines = [ lines = [
f"# План выгрузки 1С для {project_id}", f"# План выгрузки 1С для {project_id}",
@@ -210,6 +210,7 @@ def render_html5_ai_structure_result(result: dict | None) -> str:
snapshot = result.get("snapshot") or {} snapshot = result.get("snapshot") or {}
normalized = result.get("normalized") or {} normalized = result.get("normalized") or {}
source_layout = result.get("source_layout") or {} source_layout = result.get("source_layout") or {}
source_preview = list(result.get("source_preview") or [])
status = _status_text(result.get("status")) status = _status_text(result.get("status"))
return f""" return f"""
<section class="ai-structure-result" data-html5-ai-structure-status="{escape(str(result.get('status', '')))}"> <section class="ai-structure-result" data-html5-ai-structure-status="{escape(str(result.get('status', '')))}">
@@ -225,6 +226,7 @@ def render_html5_ai_structure_result(result: dict | None) -> str:
<div><dt>Объекты</dt><dd>{escape(str(normalized.get("objects", 0)))}</dd></div> <div><dt>Объекты</dt><dd>{escape(str(normalized.get("objects", 0)))}</dd></div>
</dl> </dl>
{render_html5_ai_structure_result_summary(source_layout, normalized)} {render_html5_ai_structure_result_summary(source_layout, normalized)}
{_render_ai_structure_preview_tree(source_preview)}
<div class="panel-title">Артефакты</div> <div class="panel-title">Артефакты</div>
<div class="access-operations">{''.join(f'<article class="access-card"><strong>{escape(_artifact_text(item))}</strong><small>Файл пакета структуры</small></article>' for item in artifacts)}</div> <div class="access-operations">{''.join(f'<article class="access-card"><strong>{escape(_artifact_text(item))}</strong><small>Файл пакета структуры</small></article>' for item in artifacts)}</div>
{_diagnostics(diagnostics)} {_diagnostics(diagnostics)}
@@ -365,6 +367,7 @@ def _artifact_text(value: object) -> str:
"export_plan.md": "План выгрузки", "export_plan.md": "План выгрузки",
"codex_package": "Папка для Codex", "codex_package": "Папка для Codex",
"project_layout.json": "Карта раскладки проекта", "project_layout.json": "Карта раскладки проекта",
"source_preview.json": "Краткий предпросмотр структуры",
"compact_objects.json": "Компактный индекс объектов", "compact_objects.json": "Компактный индекс объектов",
"compact_modules.json": "Компактный индекс модулей", "compact_modules.json": "Компактный индекс модулей",
"sir_snapshot.json": "Снимок графа SIR", "sir_snapshot.json": "Снимок графа SIR",
@@ -1409,6 +1409,7 @@ class AiStructurePrepareResponse(BaseModel):
files_count: int = 0 files_count: int = 0
binary_1c_files: list[dict] = Field(default_factory=list) binary_1c_files: list[dict] = Field(default_factory=list)
source_layout: dict = Field(default_factory=dict) source_layout: dict = Field(default_factory=dict)
source_preview: list[dict] = Field(default_factory=list)
artifacts: list[str] = Field(default_factory=list) artifacts: list[str] = Field(default_factory=list)
snapshot: dict | None = None snapshot: dict | None = None
normalized: dict | None = None normalized: dict | None = None
+3
View File
@@ -1731,6 +1731,7 @@ def test_ai_structure_prepare_writes_ai_ready_package(tmp_path: Path):
assert (output / "normalized_project.json").exists() assert (output / "normalized_project.json").exists()
assert (output / "sir_snapshot.json").exists() assert (output / "sir_snapshot.json").exists()
assert (output / "project_layout.json").exists() assert (output / "project_layout.json").exists()
assert (output / "source_preview.json").exists()
assert (output / "compact_objects.json").exists() assert (output / "compact_objects.json").exists()
assert (output / "compact_modules.json").exists() assert (output / "compact_modules.json").exists()
assert (codex_package / "AGENTS.md").exists() assert (codex_package / "AGENTS.md").exists()
@@ -1740,6 +1741,7 @@ def test_ai_structure_prepare_writes_ai_ready_package(tmp_path: Path):
assert (codex_package / "context" / "project-overview.md").exists() assert (codex_package / "context" / "project-overview.md").exists()
assert (codex_package / "indexes" / "codex-navigation.json").exists() assert (codex_package / "indexes" / "codex-navigation.json").exists()
assert (codex_package / "indexes" / "project-layout.json").exists() assert (codex_package / "indexes" / "project-layout.json").exists()
assert (codex_package / "indexes" / "source-preview.json").exists()
assert (codex_package / "indexes" / "objects-compact.json").exists() assert (codex_package / "indexes" / "objects-compact.json").exists()
assert (codex_package / "indexes" / "modules-compact.json").exists() assert (codex_package / "indexes" / "modules-compact.json").exists()
assert (codex_package / "indexes" / "objects.json").exists() assert (codex_package / "indexes" / "objects.json").exists()
@@ -1842,6 +1844,7 @@ def test_ai_structure_prepare_understands_configuration_and_extension_folders(tm
assert payload["status"] == "ready" assert payload["status"] == "ready"
assert payload["source_layout"]["main_configuration_root"] == "Конфигурация" assert payload["source_layout"]["main_configuration_root"] == "Конфигурация"
assert payload["source_layout"]["extension_roots"] == ["CRM"] assert payload["source_layout"]["extension_roots"] == ["CRM"]
assert any(item["label"] == "Папки расширений" for item in payload["source_preview"])
assert payload["normalized"]["extensions"] == 1 assert payload["normalized"]["extensions"] == 1
codex_package = output / payload["codex_package_folder"] codex_package = output / payload["codex_package_folder"]
layout = json.loads((codex_package / "indexes" / "project-layout.json").read_text(encoding="utf-8")) layout = json.loads((codex_package / "indexes" / "project-layout.json").read_text(encoding="utf-8"))