Overview shows workstreams by repo now and allows drilldown

This commit is contained in:
2026-05-02 12:01:45 +02:00
parent e521f267ca
commit e0f6a3b7a9
5 changed files with 260 additions and 43 deletions

View File

@@ -1,10 +1,14 @@
import uuid
import socket
from pathlib import Path
from typing import Any
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from api.database import get_session
from api.models.managed_repo import ManagedRepo
from api.models.workstream import Workstream
from api.schemas.workstream import (
WorkstreamCreate,
@@ -16,6 +20,45 @@ from api.schemas.workstream import (
router = APIRouter(prefix="/workstreams", tags=["workstreams"])
def _repo_path(repo: ManagedRepo) -> Path | None:
hostname = socket.gethostname()
candidates = []
host_paths = repo.host_paths or {}
if host_paths.get(hostname):
candidates.append(host_paths[hostname])
if repo.local_path:
candidates.append(repo.local_path)
for raw in candidates:
path = Path(raw).expanduser()
if path.is_dir():
return path
return None
def _frontmatter(path: Path) -> dict[str, Any]:
try:
text = path.read_text(encoding="utf-8")
except OSError:
return {}
if not text.startswith("---\n"):
return {}
end = text.find("\n---", 4)
if end == -1:
return {}
data: dict[str, Any] = {}
for raw_line in text[4:end].splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or ":" not in line:
continue
key, value = line.split(":", 1)
value = value.strip()
if len(value) >= 2 and value[0] == value[-1] and value[0] in {"'", '"'}:
value = value[1:-1]
data[key.strip()] = value
return data
@router.get("/", response_model=list[WorkstreamRead])
async def list_workstreams(
topic_id: uuid.UUID | None = None,
@@ -44,6 +87,37 @@ async def list_workstreams(
return list(result.scalars().all())
@router.get("/workplan-index")
async def workplan_index(session: AsyncSession = Depends(get_session)) -> dict[str, Any]:
"""Map file-backed workstream ids to their local workplan filenames."""
result = await session.execute(
select(ManagedRepo).where(ManagedRepo.status == "active").order_by(ManagedRepo.slug)
)
index: dict[str, Any] = {}
for repo in result.scalars().all():
root = _repo_path(repo)
if root is None:
continue
for directory, archived in (
(root / "workplans", False),
(root / "workplans" / "archived", True),
):
if not directory.is_dir():
continue
for path in sorted(directory.glob("*.md")):
data = _frontmatter(path)
workstream_id = data.get("state_hub_workstream_id")
if not workstream_id:
continue
index[str(workstream_id)] = {
"filename": path.name,
"relative_path": str(path.relative_to(root)),
"repo_slug": repo.slug,
"archived": archived,
}
return {"workstreams": index}
@router.post("/", response_model=WorkstreamRead, status_code=status.HTTP_201_CREATED)
async def create_workstream(
body: WorkstreamCreate,