#!/usr/bin/env python3 """Generate the reproducible LinuxCNC source and coverage seed indexes. The source index is deliberately mechanical: every file in a declared input root is recorded with its size and digest. The coverage seed is not an implementation claim; entries remain ``planned`` until an owner assigns a feature, adapter, test and oracle. """ from __future__ import annotations import argparse import hashlib import json from pathlib import Path from typing import Iterable ROOTS = ( ("doc", Path("linuxcnc/docs")), ("source", Path("linuxcnc/src")), ("config", Path("linuxcnc/configs/sim")), ("test", Path("linuxcnc/tests")), ("unit_test", Path("linuxcnc/unit_tests")), ("implementation", Path("wasm-core")), ("implementation", Path("web")), ) BUILD_FILES = ( Path("linuxcnc/VERSION"), Path("linuxcnc/meson.build"), ) def sha256(path: Path) -> str: digest = hashlib.sha256() with path.open("rb") as handle: for block in iter(lambda: handle.read(1024 * 1024), b""): digest.update(block) return digest.hexdigest() def source_kind(root_kind: str, path: Path) -> str: if root_kind == "implementation": if path.suffix in {".h", ".hpp"}: return "web_wasm_interface" if path.suffix in {".c", ".cc", ".cpp", ".mjs", ".js"}: return "web_wasm_implementation" return "web_wasm_build_or_test" if root_kind == "doc": return "documentation" if root_kind in {"test", "unit_test"}: return "test" if root_kind == "config": return "simulation_config" if path.name.endswith(".comp"): return "hal_component_source" return "source" def iter_files(repo_root: Path) -> Iterable[tuple[str, Path]]: for root_kind, relative_root in ROOTS: root = repo_root / relative_root if not root.is_dir(): raise FileNotFoundError(f"required inventory root is missing: {relative_root}") for path in sorted(path for path in root.rglob("*") if path.is_file()): yield root_kind, path for relative_path in BUILD_FILES: path = repo_root / relative_path if not path.is_file(): raise FileNotFoundError(f"required build source is missing: {relative_path}") yield "build", path def make_entry(repo_root: Path, root_kind: str, path: Path) -> dict[str, object]: relative = path.relative_to(repo_root).as_posix() source_id = f"{root_kind}:{relative}" return { "source_id": source_id, "path": relative, "kind": source_kind(root_kind, path), "bytes": path.stat().st_size, "sha256": sha256(path), } def make_manifest_entry(source: dict[str, object]) -> dict[str, object]: source_id = str(source["source_id"]) return { "id": f"seed:{source_id}", "feature_id": None, "source": source_id, "source_sha": source["sha256"], "domain": "unassigned", "kind": source["kind"], "adaptation": None, "dependencies": [], "inputs": [source["path"]], "outputs": [], "errors": [], "sim_status": "planned", "web_status": "planned", "test_id": None, "oracle": None, "golden": None, "difference": None, "coverage_dimensions": [], "context_id": None, "exclusion_reason": None, "mapping_status": "needs_owner", } def write_jsonl(path: Path, entries: Iterable[dict[str, object]]) -> None: path.parent.mkdir(parents=True, exist_ok=True) with path.open("w", encoding="utf-8", newline="\n") as handle: for entry in entries: handle.write(json.dumps(entry, ensure_ascii=False, sort_keys=True, separators=(",", ":"))) handle.write("\n") def generate(repo_root: Path, source_index: Path, manifest: Path) -> tuple[int, int]: sources = [make_entry(repo_root, kind, path) for kind, path in iter_files(repo_root)] sources.sort(key=lambda item: str(item["source_id"])) write_jsonl(source_index, sources) write_jsonl(manifest, (make_manifest_entry(source) for source in sources)) return len(sources), len(sources) def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1]) parser.add_argument("--source-index", type=Path, default=Path("coverage/source-index.jsonl")) parser.add_argument("--manifest", type=Path, default=Path("coverage/manifest.jsonl")) args = parser.parse_args() source_count, manifest_count = generate( args.repo_root.resolve(), args.repo_root.resolve() / args.source_index, args.repo_root.resolve() / args.manifest, ) print(f"source_index={source_count} manifest_seed={manifest_count}") return 0 if __name__ == "__main__": raise SystemExit(main())