#!/usr/bin/env python3 """Render a 1C task change proposal JSON as a compact Markdown report.""" from __future__ import annotations import argparse import json from pathlib import Path from typing import Any def load_json(path: Path) -> dict[str, Any]: return json.loads(path.read_text(encoding="utf-8-sig")) def line(text: str = "") -> str: return text.rstrip() + "\n" def bullet(text: str) -> str: return f"- {text}\n" def fmt_origin(origin: dict[str, Any] | None) -> str: if not origin: return "" if origin.get("extension"): return f"{origin.get('layer')}:{origin.get('extension')}" return str(origin.get("layer") or "") def fmt_path(path: str | None, line_no: Any = None) -> str: if not path: return "" suffix = f":{line_no}" if line_no else "" return f"`{path}{suffix}`" def render_target(target: dict[str, Any]) -> str: path = target.get("path") or target.get("module_path") bits = [str(target.get("kind") or ""), str(target.get("name") or "")] origin = fmt_origin(target.get("origin")) if origin: bits.append(f"({origin})") rendered = " ".join(bit for bit in bits if bit) if path: rendered += f" -> {fmt_path(path, target.get('line'))}" return rendered def render_proposal(proposal: dict[str, Any], index: int) -> str: out = "" obj = proposal.get("object") or {} out += line(f"## Proposal {index}: {obj.get('kind')}.{obj.get('name')}") if obj.get("synonym"): out += line(f"Synonym: {obj.get('synonym')}") out += line() strategy = proposal.get("write_strategy") or {} out += line("### Strategy") out += bullet(f"Mode: `{strategy.get('mode')}`") out += bullet(f"Preferred extension: `{strategy.get('preferred_extension')}`") out += bullet(f"Reason: {strategy.get('reason')}") out += line() state = proposal.get("existing_state") or {} commands = state.get("found_form_commands_or_items") or [] attrs = state.get("found_metadata_attributes") or [] code_hits = state.get("found_code_hits") or [] if commands: out += line("### Existing Form Commands/Items") for row in commands[:10]: out += bullet( f"{row.get('area')} `{row.get('name')}` on `{row.get('form')}` " f"({fmt_origin(row.get('origin'))}) {fmt_path(row.get('path'), row.get('line'))}" ) out += line() if attrs: out += line("### Related Metadata Attributes") for row in attrs[:10]: out += bullet(f"`{row.get('name')}` ({fmt_origin(row.get('origin'))})") out += line() if code_hits: out += line("### Code Hits") for row in code_hits[:12]: out += bullet(f"`{row.get('name')}` ({fmt_origin(row.get('origin'))}) {fmt_path(row.get('path'), row.get('line'))}") out += line() policy = proposal.get("target_policy") or {} write_candidates = policy.get("write_candidates") or [] references = policy.get("read_only_reference_files") or [] out += line("### Write Candidates") if write_candidates: for target in write_candidates[:20]: out += bullet(render_target(target)) else: out += bullet("No write candidate selected.") out += line() out += line("### Read-Only References") for target in references[:20]: out += bullet(render_target(target)) if len(references) > 20: out += bullet(f"... {len(references) - 20} more") out += line() out += line("### Implementation Steps") for step in proposal.get("implementation_steps") or []: out += bullet(f"{step.get('order')}. `{step.get('action')}` - {step.get('purpose') or ''}") out += line() out += line("### Validation") for check in proposal.get("validation_checks") or []: out += bullet(f"`{check.get('name')}` - {check.get('description')}") questions = proposal.get("open_questions") or [] if questions: out += line() out += line("### Open Questions") for question in questions: out += bullet(question) return out def render(data: dict[str, Any]) -> str: out = "" out += line("# 1C Task Change Proposal") out += line() out += line(f"Task: {((data.get('task') or {}).get('text') or '').strip()}") out += line(f"Schema: `{data.get('schema')}`") out += line(f"View: `{data.get('view')}`") safety = data.get("safety") or {} out += line(f"Safety: `{safety.get('mode')}`, `{safety.get('write_status')}`") out += line() for idx, proposal in enumerate(data.get("proposals") or [], start=1): out += render_proposal(proposal, idx) out += line() return out def main() -> int: parser = argparse.ArgumentParser(description="Render 1C task proposal Markdown.") parser.add_argument("--proposal", type=Path, required=True) parser.add_argument("--output", type=Path) args = parser.parse_args() markdown = render(load_json(args.proposal)) if args.output: args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_text(markdown, encoding="utf-8") print(json.dumps({"output": str(args.output)}, ensure_ascii=False)) else: print(markdown) return 0 if __name__ == "__main__": raise SystemExit(main())