nightshift/nightshift/integ_report.py
K. Hodges f7fed4535b Add tutorial integration workflow helpers
- Add `integ-test` to create, set up, validate, and run integration template tasks
  - Add `integ-report` to summarize latest integration run artifacts
  - Switch default pastebin template from model fallback to single `qwen3-coder:30b`
  - Support optional Ollama fields: `num_ctx`, `num_predict`, `seed`, and `stop`
  - Add `nightshift validate` preflight for task-specific test files
  - Update pastebin docs, config reference, and ideas tracking
  - Add tests for integration helpers, task-test validation, config parsing, and template expectations
2026-05-21 03:46:27 -07:00

72 lines
2.9 KiB
Python

"""Summarize integration run artifacts."""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import re
from .errors import NightShiftError
@dataclass(frozen=True)
class IntegrationReport:
integration_run: Path
nightshift_run: Path | None
lines: tuple[str, ...]
def build_integration_report(root: str | Path = ".", *, latest: bool = True) -> IntegrationReport:
base = Path(root).resolve() / "integ_runs"
if not base.exists():
raise NightShiftError(f"Integration report error: no integ_runs directory found: {base}")
runs = sorted((path for path in base.iterdir() if path.is_dir()), key=lambda path: path.name, reverse=True)
if not runs:
raise NightShiftError(f"Integration report error: no integration runs found under: {base}")
integration_run = runs[0] if latest else runs[0]
artifacts_root = integration_run / "project" / ".nightshift" / "runs"
if not artifacts_root.exists():
return IntegrationReport(
integration_run,
None,
("No NightShift run artifacts found. Setup may have failed before task execution.",),
)
nightshift_runs = sorted((path for path in artifacts_root.iterdir() if path.is_dir()), key=lambda path: path.name, reverse=True)
if not nightshift_runs:
return IntegrationReport(integration_run, None, ("No NightShift run directories found.",))
nightshift_run = nightshift_runs[0]
summaries = sorted(nightshift_run.glob("tasks/*/run-summary.md"))
if not summaries and (nightshift_run / "run-summary.md").exists():
summaries = [nightshift_run / "run-summary.md"]
lines = [_summarize_run_summary(path, integration_run) for path in summaries]
return IntegrationReport(integration_run, nightshift_run, tuple(lines or ("No task summaries found.",)))
def format_integration_report(report: IntegrationReport) -> str:
lines = [f"Integration run: {report.integration_run}"]
if report.nightshift_run is not None:
lines.append(f"NightShift run: {report.nightshift_run}")
lines.append("")
lines.extend(f"- {line}" for line in report.lines)
return "\n".join(lines)
def _summarize_run_summary(path: Path, integration_run: Path) -> str:
text = path.read_text(encoding="utf-8", errors="replace")
task = _field(text, "Task") or path.parent.name
status = _field(text, "Status") or "unknown"
retries = _field(text, "Retry count") or "unknown"
reason = _field(text, "Reason") or "no reason recorded"
try:
relative = path.relative_to(integration_run)
except ValueError:
relative = path
return f"{task} {status} after {retries} retries. Reason: {reason}. Artifacts: {relative.parent}"
def _field(text: str, name: str) -> str | None:
match = re.search(rf"^- {re.escape(name)}:\s*(.+)$", text, flags=re.MULTILINE)
if not match:
return None
return match.group(1).strip()