New projects from conversation (M2.5, PR B)
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 4m56s
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 4m56s
- propose_project(name, purpose, decisions) in a conversation goes up for a go-ahead (checked: a slug, not an existing project, no trailing question). - On go, the provisioning checklist (D1): a repository with a README, protection on its default branch, the agent as a collaborator, a build VM, a deploy key, and the project entry as YAML with <...> to fill in. - On "done", the agent reads its configuration again and adds the new project live (build_loop_agent(load=...); only additions), then checks what was provided (D4, verified, not assumed): permissions and protection as it sees them, then on a throwaway branch with an empty commit, a branch push must succeed and a push to the default branch and merging its own PR must be refused. The check's PR is closed and its branch deleted. Failures are said per item; nothing starts. - Checked: the tracking issue (the distilled decisions, carrying the new item's ID), and the project's item, agreed and labelled `design`, whose first milestone is ARCHITECTURE.md and the build plan. A conversation waiting on provisioning doesn't close as idle. - The walk replays exactly from its seed (item IDs too), and event IDs stay unique across simulated restarts, as a server's are. - The test config holds output to 8192 tokens, under the test key's per-request cost cap. Tests: new-project unit tests (checklist and its YAML parse as config, done before the entry, what isn't set up, a checked project's record and first PR, an existing name), protection enforced by a hook on the fake forge, the walk proposing projects and saying done, and an e2e new project in a repository the test operator provisions from the agent's checklist. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
This commit is contained in:
1 parent
b3bf6d5a6e
commit
dd6196c742
13 files changed
+723
-54
No files matched your search
@@ -14,7 +14,7 @@ from nisse_dev.cancel import Cancel
|
||||
from nisse_dev.canonical import ToolCall
|
||||
from nisse_dev.execution import SSHHost
|
||||
from nisse_dev.git import GitRepo
|
||||
from nisse_dev.gitea import Comment, Issue, PullRequest, ReviewNote
|
||||
from nisse_dev.gitea import Comment, GiteaError, Issue, PullRequest, ReviewNote
|
||||
from nisse_dev.loop.agent import LoopAgent
|
||||
from nisse_dev.loop.driver import Driver
|
||||
from nisse_dev.loop.items import ANSWER, MERGE, WAITING, loop_of
|
||||
@@ -124,6 +124,32 @@ class Forge:
|
||||
def close_pull_request(self, repo: str, index: int) -> None:
|
||||
self.prs[index]["state"] = "closed"
|
||||
|
||||
# what a new project's checks read (D4)
|
||||
protected = False
|
||||
|
||||
def protect(self) -> None:
|
||||
"""Protection as the server enforces it: pushes to main refused by a
|
||||
hook; merging needs an approval nobody here gives."""
|
||||
hook = self.bare / "hooks" / "pre-receive"
|
||||
hook.write_text(
|
||||
"#!/bin/sh\nwhile read old new ref; do\n"
|
||||
' [ "$ref" = refs/heads/main ] && echo "main is protected" && exit 1\n'
|
||||
"done\nexit 0\n"
|
||||
)
|
||||
hook.chmod(0o755)
|
||||
self.protected = True
|
||||
|
||||
def repository(self, repo: str) -> dict[str, Any]:
|
||||
return {"permissions": {"push": True}}
|
||||
|
||||
def branch(self, repo: str, name: str) -> dict[str, Any]:
|
||||
return {"protected": self.protected, "user_can_push": not self.protected}
|
||||
|
||||
def merge_pull_request(self, repo: str, index: int) -> None:
|
||||
if self.protected:
|
||||
raise GiteaError("gitea POST merge returned 405: approvals needed")
|
||||
self.merge(index)
|
||||
|
||||
# what the operator does in Gitea
|
||||
def merge(self, n: int) -> None:
|
||||
head = f"refs/heads/{self.prs[n]['head']}"
|
||||
|
||||
Reference in new issue
Block a user