The reviewer is read-only (R3), so Gitea refuses it resolving a thread.
Now each reply says whether it settles its thread, and a settled reply
carries <!-- nisse-review: settled -->. nisse-dev, as the author, resolves
every comment on that thread's line when it watches the PR, and doesn't
take the settled reply as a point to answer. scripts/resolve_settled.py
does the same for PRs made by hand.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
Archives were downloaded and unpacked afresh for every run, which doesn't
scale to very large repositories. Each repository is now cloned once, with
its whole history, under scratch/repos/<owner>/<name>/ (head/ the clone,
base/ a worktree when read). A run fetches every branch and the PR's
refs/pull/N/head, checks out the head exactly, and fails if that head is
gone. Only the latest repository is kept for now.
Git runs walled off: none of the host's git configuration, links checked
out as plain files, no hooks, submodules or LFS, only the Gitea URL's
protocol, and the token as an http.extraHeader for that URL in git's
environment only. A stop ends a git command; a cut-short first clone is
removed. The reader never shows .git. GiteaClient.archive is gone.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
Back on a PR it reviewed: a new head gets a later review of the commits
since (each one's diff, through the API) and of its earlier points; what
was said since gets replies, answered where it was said or skipped with a
reason. It signs off once and stays quiet while still happy. Past
tuning.review.max_rounds it sums up for the operator and goes quiet,
until a push or someone other than the PR's author joins in. An
operator's `stop` quiets it; `review` asks for a fresh review.
Reviews carry numbered markers and replies their point's key, so
recovery posts nothing twice.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
At bring-up, `dev/*` failed: listing an organization's repositories
(/orgs/dev/repos) needs the read:organization token scope, which the
reviewer's token doesn't have and shouldn't need. Patterns are now matched
against /repos/search, which needs only the repository scope and already
lists every repository the account can read. A pattern that matches
nothing is logged (access.no_match). owner_repositories goes.
A Gitea error at startup is now reported like a config error, not a
traceback.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
At bring-up, nisse-review read no repositories: Gitea's /user/repos leaves
out a public repository's Read collaborators, so dev/nisse never showed up.
`repositories` now holds patterns (dev/nisse, dev/*, */*) matched against
every repository the account can read, public ones included (an
organization's or user's list, or the instance's search); unset, it is
/user/repos as before. A configured repository it can't read is left out
with an oplog line.
The read-only wall (R3) is checked every round, not only at startup: an
account granted push or admin while running stops (exit 3) rather than
review on.
nisse-core's Gitea client gains GiteaNotFound for 404s, owner_repositories
and visible_repositories.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
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
What isn't the dev agent's moves to nisse-core, unchanged in behaviour,
with its tests: canonical, render, render_anthropic, llm, catalog, probe,
context, views, state, reconcile, worker, cancel, dump, transport, matrix,
execution, testing, vault, memory, oplog, envfile and gitea (whole).
- nisse_core.state.StateStore holds the tables every agent has; an
agent's subclass adds its tables (version, migrations), roles and
views. nisse_dev.state.StateStore adds the build VM blocks (v5).
- views.project takes what each role is shown, rather than a fixed table.
- Vault takes the agent's name (agent=), for provenance.
- A test that nisse_core never imports an agent.
- The loop, config, git, delegation, expert, tools and evals stay in
nisse-dev until nisse-ops has a loop to share.
Docs: the Development Plan marks M7.1 done and notes that Phase 6 waits on
the agent host and Terraform; Architecture Part L names the package.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk