A design milestone holds documents only, enforced
Found by the first local e2e run: qwen3.8-27b put a new project's code in the same PR as its ARCHITECTURE.md, with no build plan. D1 says a `design` item agrees its design before any code, and that rested on wording only. - Until a PR of a `design` item has merged, finish_milestone is sent back if the milestone changes anything but ARCHITECTURE.md and docs/, naming the paths to take out. The merge is recorded (design_merged) and code may follow. - The label's instruction and the new project's brief say so plainly: documents first, in a PR of their own; code after it has merged. Tests: a design milestone with code is sent back and only the documents are published, then code after the merge; the walk's model follows the rule. 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
ad2d090143
commit
88c7d2e8c9
6 files changed
+101
-7
No files matched your search
@@ -150,9 +150,11 @@ LABELS = {
|
||||
"discuss": "This issue is labelled discuss: talk it through with the "
|
||||
"operator (ask_human) before restating, and restate only once you agree "
|
||||
"on what is wanted.",
|
||||
"design": "This issue is labelled design: agree the design with the "
|
||||
"operator before any code. Your restatement puts the design to them; "
|
||||
"code waits for their go-ahead on it.",
|
||||
"design": "This issue is labelled design: the design comes before any "
|
||||
"code. Your first milestone is documents only: ARCHITECTURE.md and a "
|
||||
"build plan under docs/plans/, in a PR of their own (if you restate, put "
|
||||
"the design to the operator). Code starts in a later milestone, after that "
|
||||
"PR has merged; the harness refuses a design milestone with anything else.",
|
||||
}
|
||||
|
||||
|
||||
@@ -519,6 +521,9 @@ class LoopAgent:
|
||||
if pr.merged:
|
||||
self._fact(item, f"PR #{loop.pr} merged")
|
||||
loop, position = replace(loop, pr=0, pr_url="", branch=""), READY
|
||||
if "design" in loop.labels and not loop.design_merged:
|
||||
self._fact(item, "the design has merged: code may follow")
|
||||
loop = replace(loop, design_merged=True)
|
||||
elif pr.state != "open":
|
||||
self._fact(item, f"PR #{loop.pr} was closed without merging")
|
||||
loop, position = replace(loop, pr=0, pr_url="", branch=""), READY
|
||||
@@ -1047,7 +1052,8 @@ class LoopAgent:
|
||||
project.repo,
|
||||
title,
|
||||
f"{brief}\n\nFrom a conversation with the operator, agreed there "
|
||||
f"as rev-{loop.rev}. First: ARCHITECTURE.md and the build plan."
|
||||
f"as rev-{loop.rev}. First: ARCHITECTURE.md and the build plan, "
|
||||
"in a PR of documents only; code after it has merged."
|
||||
f"\n\n<!-- nisse-item: {new_id} -->",
|
||||
)
|
||||
self._gitea.assign_issue(project.repo, number, self._account)
|
||||
@@ -1211,6 +1217,32 @@ class LoopAgent:
|
||||
)
|
||||
self._last_project = here
|
||||
|
||||
def _design_problem(self, item_id: str) -> str | None:
|
||||
"""D1, D2: a `design` item agrees its design before any code. Until
|
||||
a PR of it has merged, a milestone holds documents only."""
|
||||
item = self._store.work_item(item_id)
|
||||
if item is None:
|
||||
return None
|
||||
loop = loop_of(item)
|
||||
milestones = self._at(item).milestones
|
||||
if "design" not in loop.labels or loop.design_merged or milestones is None:
|
||||
return None
|
||||
try:
|
||||
changed = milestones.changed()
|
||||
except GitError:
|
||||
return None # publishing reports what's wrong with the workspace
|
||||
code = [
|
||||
p for p in changed if p != "ARCHITECTURE.md" and not p.startswith("docs/")
|
||||
]
|
||||
if not code:
|
||||
return None
|
||||
return (
|
||||
"not published: this milestone is the design, so it holds documents "
|
||||
"only (ARCHITECTURE.md, docs/); code starts in a later milestone, "
|
||||
"after this one's PR has merged. Take these out of it (they can "
|
||||
"wait in the plan): " + ", ".join(code)
|
||||
)
|
||||
|
||||
def _converse_run(self, item: WorkItem) -> None:
|
||||
"""A conversation's turn (L1, L5): it talks, and may propose work on
|
||||
a project. No workspace, nothing to run."""
|
||||
@@ -1329,7 +1361,10 @@ class LoopAgent:
|
||||
tools=tools,
|
||||
workspace=workspace,
|
||||
handlers={"start_milestone": lambda args: self._start(item.id, args)},
|
||||
checks={"restate": _restatement_problem},
|
||||
checks={
|
||||
"restate": _restatement_problem,
|
||||
"finish_milestone": lambda args: self._design_problem(item.id),
|
||||
},
|
||||
)
|
||||
finally:
|
||||
self._running = False
|
||||
|
||||
Reference in new issue
Block a user