feat(RMGR-WP-0001): complete T05 end-to-end vertical slice
Implement observe/reconcile/update-task-status CLI path: workplan parse, JSON projection index, git-backed task status writeback with correlation events, and E2E pytest plus evidence artifacts. Finish foundation workplan.
This commit is contained in:
parent
3cc67a9bd0
commit
8b5634ff2a
15 changed files with 873 additions and 20 deletions
83
src/repo_manager/index_store.py
Normal file
83
src/repo_manager/index_store.py
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
"""Local JSON projection store for the vertical-slice proof (pre-Postgres)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
def _now() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
@dataclass
|
||||
class WorkRecordEntry:
|
||||
kind: str
|
||||
id: str | None
|
||||
status: str | None
|
||||
title: str | None
|
||||
source_path: str
|
||||
uuid: str | None = None
|
||||
parent_id: str | None = None
|
||||
extra: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class RepoIndex:
|
||||
slug: str
|
||||
repo_root: str
|
||||
head_sha: str | None
|
||||
observed_at: str
|
||||
work_records: list[WorkRecordEntry] = field(default_factory=list)
|
||||
events: list[dict[str, Any]] = field(default_factory=list)
|
||||
schema: str = "repo_manager.index.v0"
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"schema": self.schema,
|
||||
"slug": self.slug,
|
||||
"repo_root": self.repo_root,
|
||||
"head_sha": self.head_sha,
|
||||
"observed_at": self.observed_at,
|
||||
"work_records": [asdict(r) for r in self.work_records],
|
||||
"events": self.events,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any]) -> RepoIndex:
|
||||
records = [
|
||||
WorkRecordEntry(**{k: v for k, v in r.items() if k in WorkRecordEntry.__dataclass_fields__})
|
||||
for r in data.get("work_records", [])
|
||||
]
|
||||
return cls(
|
||||
slug=data["slug"],
|
||||
repo_root=data["repo_root"],
|
||||
head_sha=data.get("head_sha"),
|
||||
observed_at=data.get("observed_at") or _now(),
|
||||
work_records=records,
|
||||
events=list(data.get("events") or []),
|
||||
schema=data.get("schema") or "repo_manager.index.v0",
|
||||
)
|
||||
|
||||
|
||||
def default_index_path(repo_root: Path) -> Path:
|
||||
return repo_root / ".repo-manager" / "index.json"
|
||||
|
||||
|
||||
def save_index(index: RepoIndex, path: Path | None = None) -> Path:
|
||||
path = path or default_index_path(Path(index.repo_root))
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(index.to_dict(), indent=2) + "\n", encoding="utf-8")
|
||||
return path
|
||||
|
||||
|
||||
def load_index(path: Path) -> RepoIndex:
|
||||
return RepoIndex.from_dict(json.loads(path.read_text(encoding="utf-8")))
|
||||
|
||||
|
||||
def append_event(index: RepoIndex, event: dict[str, Any]) -> None:
|
||||
event = {**event, "emitted_at": event.get("emitted_at") or _now()}
|
||||
index.events.append(event)
|
||||
Loading…
Add table
Add a link
Reference in a new issue