M7.3: nisse-review's first slice
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m49s
CI / e2e (pull_request) Successful in 3m18s

A new package over nisse-core: it reviews every ready PR in the
repositories its account can read, and posts one COMMENT review.

- scheduler: each round, open PRs per readable repository (or the
  configured repositories); an item per PR, ready unless draft or WIP;
  closed with the PR. One review at a time, oldest first.
- reader: the repository at head and base from Gitea archives, unpacked
  and read in Python (read_file, list_files, search_code), and its issues;
  bound to the item's repository; no git, no shell.
- review: starts from the PR, commits, diff, comments, closed issues, CI;
  comment drafts a point on any existing line; post_review posts one
  COMMENT review at the head, inline where Gitea takes it, else as links in
  the summary. ask_expert; vault and memory (memory/nisse-review).
- L8: posting is an intent; recovery asks Gitea for the marked review and
  never posts twice; with Gitea down, the item waits.
- Failures retried, then said on the PR until the next push.
- Startup refuses an account that can push to or administer any repo.
- nisse-core gitea: readable_repositories, open_pull_requests, diff,
  commits, archive, issue, search_issues, reviews, post_review; PR fields.
- Bring-up: scripts/review-bringup.sh; nisse-review.example.yaml.
- Tests: unit over a fake Gitea and scripted model; a live test
  (NISSE_REVIEW_GITEA_TOKEN) reviews a PR in a fresh test repository.
- Docs: Development Plan M7.3, Tuning Guide (tuning.review.*).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
This commit is contained in:
dongarandClaude Opus 5.5 committed 2026-10-13 22:34:02 +00:00
1 parent fc71d3da75
commit f59d974a95
24 files changed
+2330 -7

No files matched your search

+47
View File
@@ -0,0 +1,47 @@
"""nisse-review's state store (architecture L8): the shared one, with its own
name and roles. A work item is one PR (R1); what the reviewer keeps about it
is in its row's data, as ``PR``."""
from dataclasses import asdict, dataclass
from typing import Any
from nisse_core.state import store
from nisse_core.state.store import WorkItem
# A review item's positions.
READY = "ready" # to review
RUNNING = "running" # a review run is under way; on boot, interrupted
REVIEWED = "reviewed" # reviewed at its head; later events are M7.5's
WAITING = "waiting" # not ready: a draft or work in progress
FAILED = "failed" # reviews kept failing; said so on the PR
CLOSED = "closed"
POST_REVIEW = "post_review" # the intent around posting one (L8)
class StateStore(store.StateStore):
AGENT = "nisse-review"
# The reviewer drives as "driver" (the shared views and tools expect
# it); "memory" is its pass at a close.
ACTORS = store.ACTORS | {"memory"}
@dataclass(frozen=True)
class PR:
repo: str # owner/name
number: int
head: str = "" # the commit last seen at the PR's head
reviewed: str = "" # the commit its review was posted on
attempts: int = 0 # failed runs at this head
def item_id(repo: str, number: int) -> str:
return f"{repo}#{number}"
def pr_of(item: WorkItem) -> PR:
return PR(**item.data["pr"])
def data(pr: PR) -> dict[str, Any]:
return {"pr": asdict(pr)}