orchestrata_ultimate.refactor.codemod.report

Reference › orchestrata_ultimate › refactor › codemod › report

The report data structures shared by the passes, the review engine, the transaction layer and the daemon. A ChangeEntry records one file's path, original and mutated LosslessModule; a ChangeSet is a tuple of entries with file_count, paths, iteration and a merge that combines two sets and keeps the earliest original tree per path, so a multi-pass run that edits one file still commits. A Finding is one review result, and a CommitReport carries the outcome of a commit: passes run, files written, findings, legacy findings and duration. CommitReport converts to and from JSON (to_json, from_json, from_dict), which is how the CLI and the daemon client exchange it. It is imported by passes, txn, workspace, server, the review modules and the persistence repository.

At a glance

Source src/orchestrata_ultimate/refactor/codemod/report.py
Lines of code 181
Classes 4
Functions 0
Imports (internal) orchestrata_ultimate.refactor.codemod._types, orchestrata_ultimate.refactor.codemod.observability.events, orchestrata_ultimate.refactor.ctok
Imported by orchestrata_ultimate.refactor.codemod, orchestrata_ultimate.refactor.codemod.analysis.breaking_change, orchestrata_ultimate.refactor.codemod.client, orchestrata_ultimate.refactor.codemod.passes, orchestrata_ultimate.refactor.codemod.persistence.repository, orchestrata_ultimate.refactor.codemod.review, orchestrata_ultimate.refactor.codemod.review_catalogue, orchestrata_ultimate.refactor.codemod.review_checks, orchestrata_ultimate.refactor.codemod.review_engine, orchestrata_ultimate.refactor.codemod.server, orchestrata_ultimate.refactor.codemod.txn, orchestrata_ultimate.refactor.codemod.workspace, scripts.codemod
Tests that import it tests/refactor/codemod/analysis/test_breaking_change.py, tests/refactor/codemod/analysis/test_read_only.py, tests/refactor/codemod/test_cli.py, tests/refactor/codemod/test_ipc_protocol.py, tests/refactor/codemod/test_observability.py, tests/refactor/codemod/test_observability_wiring.py, tests/refactor/codemod/test_persistence_repository_coverage.py, tests/refactor/codemod/test_review_checks.py, tests/refactor/codemod/test_review_coverage.py, tests/refactor/codemod/test_review_engine.py, tests/refactor/codemod/test_review_engine_commit_wiring.py, tests/refactor/codemod/test_review_passes.py, tests/refactor/codemod/test_transactional_writeback.py, tests/refactor/codemod/test_transform_passes.py, tests/refactor/codemod/test_workspace_load.py, tests/scripts/test_codemod_cli_daemon.py

Class diagram

Class diagram orchestrata_ultimate.refactor.codemod.report

Dependency diagram

Dependency diagram orchestrata_ultimate.refactor.codemod.report

API

Classes

ChangeEntry (dataclass)

Decorators: dataclass(frozen=True, slots=True) · Line 35

One mutated lossless module with metadata.

Attribute Type Default
path str
original_tree LosslessModule
mutated_tree LosslessModule
pass_name str
description str ''
Method Signature Summary
original_bytes property original_bytes() -> bytes Pre-mutation on-disk bytes (for rollback).

ChangeSet (dataclass)

Decorators: dataclass(frozen=True, slots=True) · Line 55

Immutable collection of ChangeEntry objects.

Attribute Type Default
entries tuple[ChangeEntry, ...] ()
Method Signature Summary
file_count property file_count() -> int Number of changed files.
paths property paths() -> frozenset[str] Set of changed file paths.
merge merge(other: ChangeSet) -> ChangeSet Combine two ChangeSets for a multi-pass run.
__len__ __len__() -> int
__iter__ __iter__() -> Iterator[ChangeEntry]

Finding (dataclass)

Decorators: dataclass(frozen=True, slots=True) · Line 94

One review finding (error or warning).

Attribute Type Default
severity Literal['error', 'warning']
rule str
path str
line int | None
message str
proof_tree dict[str, Any] | None None

CommitReport (dataclass)

Decorators: dataclass(frozen=True, slots=True) · Line 106

Machine-readable commit report (JSON-serializable).

Attribute Type Default
success bool
files_written tuple[str, ...]
findings tuple[Finding, ...]
duration_seconds float
dry_run bool
passes_run tuple[str, ...] ()
diffstat str ''
correlation_id str ''
legacy_findings tuple[Finding, ...] ()
Method Signature Summary
to_json to_json() -> str Serialize to JSON string.
from_dict classmethod from_dict(data: dict[str, Any]) -> CommitReport Rebuild a report from the dict to_json serializes.
from_json classmethod from_json(text: str) -> CommitReport Rebuild a report from the string to_json returns.

Source

Permalink: https://gitlab.com/MSamy/orchestrata-ultimate-architecture/-/blob/a4cbc2bdf/src/orchestrata_ultimate/refactor/codemod/report.py

"""SPEC-110 codemod engine — report data structures.

Provides ChangeEntry, ChangeSet, Finding, CommitReport, WorkspaceStats,
and CodemodEvent for the codemod engine's data flow.
"""

from __future__ import annotations

import json
from collections.abc import Iterator
from dataclasses import dataclass, replace
from typing import TYPE_CHECKING, Any, Literal

from orchestrata_ultimate.refactor.codemod._types import WorkspaceStats
from orchestrata_ultimate.refactor.codemod.observability.events import (
    CodemodEvent,
    CodemodEventType,
)

if TYPE_CHECKING:
    from orchestrata_ultimate.refactor.ctok import LosslessModule

__all__ = [
    "ChangeEntry",
    "ChangeSet",
    "Finding",
    "CommitReport",
    "WorkspaceStats",
    "CodemodEvent",
    "CodemodEventType",
]


@dataclass(frozen=True, slots=True)
class ChangeEntry:
    """One mutated lossless module with metadata."""

    path: str
    original_tree: LosslessModule
    mutated_tree: LosslessModule
    pass_name: str
    description: str = ""

    @property
    def original_bytes(self) -> bytes:
        """Pre-mutation on-disk bytes (for rollback).

        ``LosslessModule.code`` is already ``bytes`` (byte-identical to the
        on-disk source), so this is a direct hand-back -- no re-encode.
        """
        return self.original_tree.code


@dataclass(frozen=True, slots=True)
class ChangeSet:
    """Immutable collection of ChangeEntry objects."""

    entries: tuple[ChangeEntry, ...] = ()

    @property
    def file_count(self) -> int:
        """Number of changed files."""
        return len(self.entries)

    @property
    def paths(self) -> frozenset[str]:
        """Set of changed file paths."""
        return frozenset(e.path for e in self.entries)

    def merge(self, other: ChangeSet) -> ChangeSet:
        """Combine two ChangeSets for a multi-pass run.

        For a path touched by both, the LATER entry's ``mutated_tree`` and
        metadata win, but the EARLIEST entry's ``original_tree`` is kept: it
        is the load-time tree whose bytes are on disk, which is what the
        transactional write-back compares against (REQ-110-013/014).
        """
        merged: dict[str, ChangeEntry] = {e.path: e for e in self.entries}
        for entry in other.entries:
            earlier = merged.get(entry.path)
            if earlier is not None:
                entry = replace(entry, original_tree=earlier.original_tree)
            merged[entry.path] = entry
        return ChangeSet(entries=tuple(merged.values()))

    def __len__(self) -> int:
        return len(self.entries)

    def __iter__(self) -> Iterator[ChangeEntry]:
        return iter(self.entries)


@dataclass(frozen=True, slots=True)
class Finding:
    """One review finding (error or warning)."""

    severity: Literal["error", "warning"]
    rule: str
    path: str
    line: int | None
    message: str
    proof_tree: dict[str, Any] | None = None


@dataclass(frozen=True, slots=True)
class CommitReport:
    """Machine-readable commit report (JSON-serializable).

    ``findings`` is the veto set; ``legacy_findings`` holds the findings the
    original trees already carried (default engine, ADR-0073), reported but
    never vetoing.
    """

    success: bool
    files_written: tuple[str, ...]
    findings: tuple[Finding, ...]
    duration_seconds: float
    dry_run: bool
    passes_run: tuple[str, ...] = ()
    diffstat: str = ""
    correlation_id: str = ""
    legacy_findings: tuple[Finding, ...] = ()

    def to_json(self) -> str:
        """Serialize to JSON string."""

        def _finding(f: Finding) -> dict[str, Any]:
            return {
                "severity": f.severity,
                "rule": f.rule,
                "path": f.path,
                "line": f.line,
                "message": f.message,
            }

        data = {
            "success": self.success,
            "files_written": list(self.files_written),
            "findings": [_finding(f) for f in self.findings],
            "legacy_findings": [_finding(f) for f in self.legacy_findings],
            "duration_seconds": self.duration_seconds,
            "dry_run": self.dry_run,
            "passes_run": list(self.passes_run),
            "diffstat": self.diffstat,
            "correlation_id": self.correlation_id,
        }
        return json.dumps(data, indent=2)

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> CommitReport:
        """Rebuild a report from the dict ``to_json`` serializes.

        The inverse of ``to_json`` for every field it writes; a finding's
        ``proof_tree`` is not serialized, so it comes back ``None``.
        """

        def _finding(item: dict[str, Any]) -> Finding:
            return Finding(
                severity=item["severity"],
                rule=item["rule"],
                path=item["path"],
                line=item["line"],
                message=item["message"],
            )

        return cls(
            success=data["success"],
            files_written=tuple(data["files_written"]),
            findings=tuple(_finding(f) for f in data["findings"]),
            duration_seconds=data["duration_seconds"],
            dry_run=data["dry_run"],
            passes_run=tuple(data.get("passes_run", ())),
            diffstat=data.get("diffstat", ""),
            correlation_id=data.get("correlation_id", ""),
            legacy_findings=tuple(_finding(f) for f in data.get("legacy_findings", ())),
        )

    @classmethod
    def from_json(cls, text: str) -> CommitReport:
        """Rebuild a report from the string ``to_json`` returns."""
        return cls.from_dict(json.loads(text))