feat: sync Ralph stories with Gitea issues
This commit is contained in:
@@ -36,6 +36,22 @@ Meshnet continues to own registration, coverage, existing route selection/load b
|
||||
|
||||
The project-owned `ShardEngine` hides llama.cpp internals. A worker loads one exact artifact/recipe/range identity. Default tests use fake/tiny fixtures. Real runs are opt-in, preserve raw metrics, and never download models under `/home`.
|
||||
|
||||
## Gitea issue synchronization
|
||||
|
||||
Gitea is a projection of `prd.json`, never a competing source of truth. Before and after every supervised Ralph run, invoke:
|
||||
|
||||
```bash
|
||||
python3 scripts/ralph_gitea_sync.py sync
|
||||
```
|
||||
|
||||
For a complete Ralph invocation with automatic state reconciliation, use:
|
||||
|
||||
```bash
|
||||
scripts/ralph-gitea-run.sh ralph-tui run --prd .scratch/distributed-gguf-runtime/prd.json --agent claude --model sonnet --iterations 1 --no-tui --no-setup --direct-merge --no-sandbox
|
||||
```
|
||||
|
||||
The sync creates/reconciles one Gitea issue per `DGR-*` story, creates missing labels/milestones, closes issues whose `passes` is true, marks the selected next eligible story `status:in-progress`, and marks blocked stories `status:blocked`. `gitea-issues.json` is a derived mapping only.
|
||||
|
||||
## Evidence and completion
|
||||
|
||||
Each story writes `/run/media/popov/d/DEV/repos/d-popov.com/AI/.claude/worktrees/distributed-gguf-runtime/.scratch/distributed-gguf-runtime/evidence/<DGR-ID>/README.md` with exact files, commands/results, limitations, identities, and dependent-story handoff. Only `prd.json` may record `passes`; all 55 are false now. Generated Markdown cannot override it. One scoped commit per story is expected during future execution, but this plan materialization is intentionally uncommitted.
|
||||
Each story writes `/run/media/popov/d/DEV/repos/d-popov.com/AI/.claude/worktrees/distributed-gguf-runtime/.scratch/distributed-gguf-runtime/evidence/<DGR-ID>/README.md` with exact files, commands/results, limitations, identities, and dependent-story handoff. Only `prd.json` may record `passes`; DGR-017 and DGR-018 are complete and DGR-019 onward remain false. Generated Markdown and Gitea issues cannot override it. One scoped commit per story is expected during future execution.
|
||||
|
||||
335
.scratch/distributed-gguf-runtime/gitea-issues.json
Normal file
335
.scratch/distributed-gguf-runtime/gitea-issues.json
Normal file
@@ -0,0 +1,335 @@
|
||||
{
|
||||
"repository": "https://git.d-popov.com/popov/neuron-tai",
|
||||
"stories": {
|
||||
"DGR-017": {
|
||||
"number": 1,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/1",
|
||||
"state": "closed",
|
||||
"status": "completed"
|
||||
},
|
||||
"DGR-018": {
|
||||
"number": 2,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/2",
|
||||
"state": "closed",
|
||||
"status": "completed"
|
||||
},
|
||||
"DGR-019": {
|
||||
"number": 3,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/3",
|
||||
"state": "open",
|
||||
"status": "ready"
|
||||
},
|
||||
"DGR-020": {
|
||||
"number": 4,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/4",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-021": {
|
||||
"number": 5,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/5",
|
||||
"state": "open",
|
||||
"status": "in-progress"
|
||||
},
|
||||
"DGR-022": {
|
||||
"number": 6,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/6",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-023": {
|
||||
"number": 7,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/7",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-024": {
|
||||
"number": 8,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/8",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-025": {
|
||||
"number": 9,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/9",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-026": {
|
||||
"number": 10,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/10",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-027": {
|
||||
"number": 11,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/11",
|
||||
"state": "open",
|
||||
"status": "ready"
|
||||
},
|
||||
"DGR-028": {
|
||||
"number": 12,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/12",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-029": {
|
||||
"number": 13,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/13",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-030": {
|
||||
"number": 14,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/14",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-031": {
|
||||
"number": 15,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/15",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-032": {
|
||||
"number": 16,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/16",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-033": {
|
||||
"number": 17,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/17",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-034": {
|
||||
"number": 18,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/18",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-035": {
|
||||
"number": 19,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/19",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-036": {
|
||||
"number": 20,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/20",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-037": {
|
||||
"number": 21,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/21",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-038": {
|
||||
"number": 22,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/22",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-039": {
|
||||
"number": 23,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/23",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-040": {
|
||||
"number": 24,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/24",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-041": {
|
||||
"number": 25,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/25",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-042": {
|
||||
"number": 26,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/26",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-043": {
|
||||
"number": 27,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/27",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-044": {
|
||||
"number": 28,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/28",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-045": {
|
||||
"number": 29,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/29",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-046": {
|
||||
"number": 30,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/30",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-047": {
|
||||
"number": 31,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/31",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-048": {
|
||||
"number": 32,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/32",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-049": {
|
||||
"number": 33,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/33",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-050": {
|
||||
"number": 34,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/34",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-051": {
|
||||
"number": 35,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/35",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-052": {
|
||||
"number": 36,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/36",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-053": {
|
||||
"number": 37,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/37",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-054": {
|
||||
"number": 38,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/38",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-055": {
|
||||
"number": 39,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/39",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-056": {
|
||||
"number": 40,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/40",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-057": {
|
||||
"number": 41,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/41",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-058": {
|
||||
"number": 42,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/42",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-059": {
|
||||
"number": 43,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/43",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-060": {
|
||||
"number": 44,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/44",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-061": {
|
||||
"number": 45,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/45",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-062": {
|
||||
"number": 46,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/46",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-063": {
|
||||
"number": 47,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/47",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-064": {
|
||||
"number": 48,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/48",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-065": {
|
||||
"number": 49,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/49",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-066": {
|
||||
"number": 50,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/50",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-067": {
|
||||
"number": 51,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/51",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-068": {
|
||||
"number": 52,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/52",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-069": {
|
||||
"number": 53,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/53",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-070": {
|
||||
"number": 54,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/54",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
},
|
||||
"DGR-071": {
|
||||
"number": 55,
|
||||
"url": "https://git.d-popov.com/popov/neuron-tai/issues/55",
|
||||
"state": "open",
|
||||
"status": "blocked"
|
||||
}
|
||||
}
|
||||
}
|
||||
12
scripts/ralph-gitea-run.sh
Normal file
12
scripts/ralph-gitea-run.sh
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run one or more supervised Ralph iterations with Gitea state reconciliation.
|
||||
# The canonical PRD remains authoritative; Gitea is updated before and after Ralph.
|
||||
set -u
|
||||
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
cd "$ROOT"
|
||||
|
||||
python3 scripts/ralph_gitea_sync.py sync
|
||||
ralph_status=0
|
||||
"$@" || ralph_status=$?
|
||||
python3 scripts/ralph_gitea_sync.py sync
|
||||
exit "$ralph_status"
|
||||
206
scripts/ralph_gitea_sync.py
Normal file
206
scripts/ralph_gitea_sync.py
Normal file
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Synchronize the authoritative Ralph PRD with Gitea issues.
|
||||
|
||||
Gitea is a projection of prd.json. Story completion remains authoritative in
|
||||
prd.json; this tool only creates/reconciles issue bodies, labels, milestones,
|
||||
and open/closed/in-progress presentation.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_FEATURE = ROOT / ".scratch" / "distributed-gguf-runtime"
|
||||
STATUS_LABELS = {
|
||||
"ready": ("status:ready", "Ready for Ralph execution"),
|
||||
"in-progress": ("status:in-progress", "Currently selected by Ralph"),
|
||||
"blocked": ("status:blocked", "Blocked by incomplete dependencies or human gate"),
|
||||
"completed": ("status:completed", "Completed and verified in prd.json"),
|
||||
}
|
||||
|
||||
|
||||
def credential_for(host: str) -> tuple[str, str]:
|
||||
result = subprocess.run(
|
||||
["git", "credential", "fill"],
|
||||
input=f"protocol=https\nhost={host}\n\n",
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
values = dict(line.split("=", 1) for line in result.stdout.splitlines() if "=" in line)
|
||||
username, password = values.get("username"), values.get("password")
|
||||
if not password:
|
||||
raise RuntimeError(f"No Git credential available for {host}")
|
||||
return username or "", password
|
||||
|
||||
|
||||
class Gitea:
|
||||
def __init__(self, base_url: str, username: str, password: str, dry_run: bool = False):
|
||||
self.base = base_url.rstrip("/")
|
||||
self.auth = "Basic " + base64.b64encode(f"{username}:{password}".encode()).decode()
|
||||
self.dry_run = dry_run
|
||||
|
||||
def request(self, method: str, path: str, payload: Any = None) -> Any:
|
||||
url = self.base + path
|
||||
body = None if payload is None else json.dumps(payload).encode()
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
data=body,
|
||||
method=method,
|
||||
headers={
|
||||
"Authorization": self.auth,
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
)
|
||||
if self.dry_run and method not in {"GET", "HEAD"}:
|
||||
print(f"DRY-RUN {method} {path}")
|
||||
return {}
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=60) as response:
|
||||
raw = response.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
except urllib.error.HTTPError as exc:
|
||||
detail = exc.read().decode(errors="replace")[:500]
|
||||
raise RuntimeError(f"Gitea {method} {path} -> HTTP {exc.code}: {detail}") from exc
|
||||
|
||||
def paginate(self, path: str) -> list[dict[str, Any]]:
|
||||
result: list[dict[str, Any]] = []
|
||||
page = 1
|
||||
while True:
|
||||
separator = "&" if "?" in path else "?"
|
||||
batch = self.request("GET", f"{path}{separator}limit=50&page={page}")
|
||||
if not batch:
|
||||
return result
|
||||
result.extend(batch)
|
||||
if len(batch) < 50:
|
||||
return result
|
||||
page += 1
|
||||
|
||||
|
||||
def color(name: str) -> str:
|
||||
return "#" + hashlib.sha256(name.encode()).hexdigest()[:6]
|
||||
|
||||
|
||||
def load_schema(feature: Path) -> Any:
|
||||
module_path = ROOT / "scripts" / "ralph_prd_schema.py"
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location("ralph_prd_schema", module_path)
|
||||
assert spec is not None and spec.loader is not None
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def issue_body(schema: Any, story: dict[str, Any]) -> str:
|
||||
data = schema.load_prd(DEFAULT_FEATURE / "prd.json")
|
||||
return schema.render_issue_markdown(story, data) + f"\n<!-- ralph-story-id: {story['id']} -->\n"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("command", choices=["sync"])
|
||||
parser.add_argument("--feature-dir", type=Path, default=DEFAULT_FEATURE)
|
||||
parser.add_argument("--repo-url", default="https://git.d-popov.com/popov/neuron-tai")
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
feature = args.feature_dir.resolve()
|
||||
prd_path = feature / "prd.json"
|
||||
data = json.loads(prd_path.read_text())
|
||||
stories = data["userStories"]
|
||||
repo = urllib.parse.urlparse(args.repo_url)
|
||||
if not repo.hostname or not repo.path.strip("/"):
|
||||
raise SystemExit("--repo-url must be an HTTPS Gitea repository URL")
|
||||
owner, name = repo.path.strip("/").removesuffix(".git").split("/", 1)
|
||||
api_base = f"{repo.scheme}://{repo.hostname}/api/v1/repos/{owner}/{name}"
|
||||
username, password = credential_for(repo.hostname)
|
||||
gitea = Gitea(api_base, username, password, args.dry_run)
|
||||
schema = load_schema(feature)
|
||||
schema_errors = schema.validate_backlog(data)
|
||||
if schema_errors:
|
||||
raise SystemExit("PRD validation failed:\n" + "\n".join(schema_errors))
|
||||
|
||||
labels = {x["name"]: x for x in gitea.paginate("/labels")}
|
||||
milestones = {x["title"]: x for x in gitea.paginate("/milestones?state=all")}
|
||||
issue_list = gitea.paginate("/issues?state=all")
|
||||
by_story = {}
|
||||
for issue in issue_list:
|
||||
marker = f"<!-- ralph-story-id: DGR-"
|
||||
body = issue.get("body") or ""
|
||||
if marker in body:
|
||||
story_id = body.split(marker, 1)[1].split(" -->", 1)[0]
|
||||
by_story["DGR-" + story_id] = issue
|
||||
elif issue.get("title", "").startswith("DGR-"):
|
||||
by_story[issue["title"].split(":", 1)[0]] = issue
|
||||
|
||||
def ensure_label(name_: str, description: str = "") -> dict[str, Any]:
|
||||
if name_ in labels:
|
||||
return labels[name_]
|
||||
created = gitea.request("POST", "/labels", {"name": name_, "color": color(name_), "description": description})
|
||||
if args.dry_run:
|
||||
created = {"id": -len(labels) - 1, "name": name_}
|
||||
labels[name_] = created
|
||||
return created
|
||||
|
||||
for story in stories:
|
||||
for label in story["labels"]:
|
||||
ensure_label(label)
|
||||
for label, description in STATUS_LABELS.values():
|
||||
ensure_label(label, description)
|
||||
for milestone in data.get("milestones", []):
|
||||
title = milestone["id"]
|
||||
if title not in milestones:
|
||||
milestones[title] = gitea.request("POST", "/milestones", {"title": title, "description": milestone["name"] + ": " + milestone["outcome"]})
|
||||
if args.dry_run:
|
||||
milestones[title] = {"id": -len(milestones), "title": title}
|
||||
|
||||
passed = {s["id"] for s in stories if s.get("passes")}
|
||||
next_story = next(
|
||||
(
|
||||
s
|
||||
for s in stories
|
||||
if not s.get("passes")
|
||||
and s.get("triage") == "ready-for-agent"
|
||||
and all(d in passed for d in s["dependsOn"])
|
||||
),
|
||||
None,
|
||||
)
|
||||
mapping = {}
|
||||
for story in stories:
|
||||
sid = story["id"]
|
||||
desired_status = "completed" if story.get("passes") else ("in-progress" if next_story and sid == next_story["id"] else ("ready" if all(d in passed for d in story["dependsOn"]) else "blocked"))
|
||||
label_names = list(dict.fromkeys(story["labels"] + [STATUS_LABELS[desired_status][0]]))
|
||||
label_ids = [ensure_label(label)["id"] for label in label_names]
|
||||
milestone_id = milestones[story["milestone"]]["id"]
|
||||
title = f"{sid}: {story['title']}"
|
||||
body = issue_body(schema, story)
|
||||
issue = by_story.get(sid)
|
||||
payload = {"title": title, "body": body, "labels": label_ids, "milestone": milestone_id, "state": "closed" if story.get("passes") else "open"}
|
||||
if issue is None:
|
||||
issue = gitea.request("POST", "/issues", payload)
|
||||
print(f"created {sid} #{issue.get('number')} {desired_status}")
|
||||
else:
|
||||
issue = gitea.request("PATCH", f"/issues/{issue['number']}", payload)
|
||||
print(f"reconciled {sid} #{issue.get('number')} {desired_status}")
|
||||
mapping[sid] = {"number": issue.get("number"), "url": issue.get("html_url"), "state": "closed" if story.get("passes") else "open", "status": desired_status}
|
||||
|
||||
if not args.dry_run:
|
||||
(feature / "gitea-issues.json").write_text(json.dumps({"repository": args.repo_url, "stories": mapping}, indent=2) + "\n")
|
||||
print(f"synced={len(stories)} next={next_story['id'] if next_story else 'none'} dry_run={args.dry_run}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user