Initial SQL-only 1C adapter baseline

This commit is contained in:
2026-07-22 03:03:47 +03:00
commit e2503b77e7
545 changed files with 184711 additions and 0 deletions
+201
View File
@@ -0,0 +1,201 @@
#!/usr/bin/env python3
"""Create a review bundle from a ready 1C patch workspace without applying it."""
from __future__ import annotations
import argparse
import json
import shutil
import zipfile
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from check_1c_patch_preflight import build_preflight
from check_1c_patch_bundle import check_bundle
from diff_1c_patch_workspace import build_diff
from render_1c_patch_preflight_markdown import render as render_preflight_markdown
def load_json(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8-sig"))
def write_json(path: Path, data: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def safe_relative(relative_path: str) -> Path:
path = Path(relative_path.replace("\\", "/"))
if path.is_absolute() or ".." in path.parts or not str(path):
raise SystemExit(f"Unsafe relative path: {relative_path}")
return path
def slug_from_workspace(workspace: Path) -> str:
return workspace.name or "onec-patch"
def modified_records(workspace: Path, diff: dict[str, Any]) -> list[dict[str, Any]]:
manifest = load_json(workspace / "manifest.json")
records_by_rel = {
str(record.get("relative_path") or "").replace("\\", "/"): record
for record in manifest.get("files") or []
}
result = []
for item in diff.get("files") or []:
if item.get("status") != "modified":
continue
relative = str(item.get("relative_path") or "").replace("\\", "/")
record = dict(records_by_rel.get(relative) or {})
record["relative_path"] = relative
record["diff"] = {
key: (item.get("diff") or {}).get(key)
for key in ("added_lines", "removed_lines", "hunks", "patch_truncated", "patch_chars")
}
record["sha256"] = item.get("sha256")
result.append(record)
return result
def render_readme(bundle: dict[str, Any]) -> str:
lines = [
"# 1C Patch Review Bundle",
"",
f"Workspace: `{bundle.get('workspace')}`",
f"Created UTC: `{bundle.get('created_at_utc')}`",
f"Preflight status: `{(bundle.get('preflight') or {}).get('status')}`",
f"Preferred extension: `{bundle.get('preferred_extension')}`",
"",
"## Rules",
"",
"- This bundle is for review and disposable-base validation.",
"- It does not apply changes to SQL, Config, ConfigSave, ConfigCAS, or source extension files.",
"- Validate loading/packaging in a disposable 1C base before any production action.",
"",
"## Modified Files",
"",
]
for item in bundle.get("files") or []:
diff = item.get("diff") or {}
lines.append(
f"- `{item.get('relative_path')}` ({item.get('kind')}, +{diff.get('added_lines')}/-{diff.get('removed_lines')}, hunks={diff.get('hunks')})"
)
lines.extend(
[
"",
"## Contents",
"",
"- `files/`: modified working files by extension-relative path.",
"- `manifest.json`: machine-readable bundle manifest.",
"- `preflight.json`: full preflight evidence.",
"- `preflight.md`: human-readable preflight summary.",
"- `diff.json`: workspace diff evidence.",
]
)
return "\n".join(lines) + "\n"
def create_bundle(workspace: Path, output_root: Path, slug: str | None, *, force: bool, max_patch_chars: int) -> dict[str, Any]:
preflight = build_preflight(workspace, max_patch_chars=max_patch_chars)
if preflight.get("status") != "ready_for_review":
raise SystemExit(f"Workspace is not ready for review: {preflight.get('status')}")
diff = build_diff(workspace, max_patch_chars=max_patch_chars)
records = modified_records(workspace, diff)
if not records:
raise SystemExit("No modified files to bundle.")
manifest = load_json(workspace / "manifest.json")
bundle_slug = slug or slug_from_workspace(workspace)
bundle_dir = output_root / bundle_slug
if bundle_dir.exists() and not force:
raise SystemExit(f"Bundle already exists: {bundle_dir}. Use --force to replace.")
if bundle_dir.exists():
shutil.rmtree(bundle_dir)
files_root = bundle_dir / "files"
files_root.mkdir(parents=True, exist_ok=True)
copied = []
for record in records:
rel = safe_relative(str(record.get("relative_path") or ""))
src = workspace / "working" / rel
dst = files_root / rel
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dst)
copied.append({**record, "bundle_path": str(Path("files") / rel)})
bundle = {
"schema": "onec_patch_bundle.v1",
"created_at_utc": datetime.now(timezone.utc).isoformat(),
"workspace": str(workspace),
"source_manifest": str(workspace / "manifest.json"),
"preferred_extension": manifest.get("preferred_extension"),
"extension_root": manifest.get("extension_root"),
"task": manifest.get("task"),
"preflight": {
"schema": preflight.get("schema"),
"status": preflight.get("status"),
"passed": preflight.get("passed"),
"diff_summary": preflight.get("diff_summary"),
"gates": preflight.get("gates"),
},
"files": copied,
"counts": {
"files": len(copied),
"added_lines": sum(((item.get("diff") or {}).get("added_lines") or 0) for item in copied),
"removed_lines": sum(((item.get("diff") or {}).get("removed_lines") or 0) for item in copied),
"hunks": sum(((item.get("diff") or {}).get("hunks") or 0) for item in copied),
},
}
write_json(bundle_dir / "manifest.json", bundle)
write_json(bundle_dir / "preflight.json", preflight)
(bundle_dir / "preflight.md").write_text(render_preflight_markdown(preflight), encoding="utf-8")
write_json(bundle_dir / "diff.json", diff)
(bundle_dir / "README.md").write_text(render_readme(bundle), encoding="utf-8")
zip_path = bundle_dir.with_suffix(".zip")
if zip_path.exists():
zip_path.unlink()
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as archive:
for path in sorted(bundle_dir.rglob("*")):
if path.is_file():
archive.write(path, path.relative_to(bundle_dir))
bundle_check = check_bundle(bundle_dir, zip_path)
if not bundle_check.get("passed"):
raise SystemExit(f"Created bundle failed validation: {bundle_check.get('counts')}")
return {
"schema": "onec_patch_bundle_creation.v1",
"bundle_dir": str(bundle_dir),
"zip_path": str(zip_path),
"manifest": str(bundle_dir / "manifest.json"),
"preflight_status": preflight.get("status"),
"bundle_check": {
"schema": bundle_check.get("schema"),
"passed": bundle_check.get("passed"),
"counts": bundle_check.get("counts"),
},
"counts": bundle["counts"],
}
def main() -> int:
parser = argparse.ArgumentParser(description="Create a review bundle from a ready 1C patch workspace.")
parser.add_argument("--workspace", type=Path, required=True)
parser.add_argument("--output-root", type=Path, default=Path("reports/1c-sql/upo/patch-bundles"))
parser.add_argument("--slug")
parser.add_argument("--force", action="store_true")
parser.add_argument("--max-patch-chars", type=int, default=200000)
parser.add_argument("--output", type=Path)
args = parser.parse_args()
result = create_bundle(args.workspace, args.output_root, args.slug, force=args.force, max_patch_chars=args.max_patch_chars)
if args.output:
write_json(args.output, result)
print(json.dumps(result, ensure_ascii=False))
return 0
if __name__ == "__main__":
raise SystemExit(main())