diff options
| author | godosa <godosa@godosa.eu> | 2026-10-07 09:37:36 +0200 |
|---|---|---|
| committer | godosa <godosa@godosa.eu> | 2026-10-07 09:37:36 +0200 |
| commit | 811de09feb866c5b344034399154bde77acdd5ba (patch) | |
| tree | 81bd100b71b883192970c40395954a49f9a9eaff | |
| parent | 4743282cde2cd3d6ec767e93564b2121c6d643e4 (diff) | |
| download | workflow-811de09feb866c5b344034399154bde77acdd5ba.tar.gz workflow-811de09feb866c5b344034399154bde77acdd5ba.zip | |
split projects 5/6: wf orch pick/post use the code repo; push-failed stops the lane
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JEAjUkQRrCYX5MZhWxdtj2
| -rw-r--r-- | CHANGES.md | 1 | ||||
| -rw-r--r-- | tests/test_split.py | 27 | ||||
| -rwxr-xr-x | wf.py | 15 |
3 files changed, 38 insertions, 5 deletions
@@ -1,4 +1,5 @@ # Changes (newest first) +- 2026-10-07 split projects 5/6: `wf orch pick/post` use the code repo (worktrees, branch/merge checks, commit sha); `.wf/push-failed` after a done worker stops the lane (`push-failed`). Projects: nothing. - 2026-10-07 split projects 4/6: workflow.toml `push` (cwd root, env WF_MAIN), `wf push`, `.wf/push-failed` marker (`wf next` shows it); finish report gains ` push-failed <rc>`. Projects: split projects set push (follow-up migration task). - 2026-10-07 split projects 3/6: `wf finish` / `wf merge` / `wf wip` across code + private repo: paths under the private root are committed there (with TASKS/archive, message `<msg> (code <sha>)`), code paths on the branch; no code paths → no code commit; report `commit <code|-> books <private>`; `wf done` merge advice names the private books. Projects: nothing. - 2026-10-07 split projects 2/6: `wf start` makes code worktrees (branch in the code repo, `.wf-home` pointing at the private project); `wf next` multi-session advice names `wf start` in the code repo. Projects: nothing. diff --git a/tests/test_split.py b/tests/test_split.py index 4182ce4..965f667 100644 --- a/tests/test_split.py +++ b/tests/test_split.py @@ -231,3 +231,30 @@ class PushSplitTest(SplitCli): project=False, cwd=self.wt, env=IDENT) self.assertEqual(code, 0, out + err) self.assertEqual((self.root / "pushed-from.txt").read_text().strip(), str(self.root)) + + +class OrchSplitTest(SplitCli): + def setUp(self): + super().setUp() + self.wf("set", "t-three", "--model", "sonnet", "--done", "x") + + def test_pick_worktree_in_code_repo(self): + code, out, err = self.wf("orch", "pick", "fast", "--id", "t-three", project=False, env=IDENT) + self.assertEqual(code, 0, out + err) + self.assertIn(f"Main tree: {self.root} Worktree: {self.code / '.worktrees' / 'fast'} Branch: fast/t-three", out) + + def test_post_done_checks_code_branch_and_stops_on_push_failed(self): + self.wf("orch", "pick", "fast", "--id", "t-three", project=False, env=IDENT) + wt = self.code / ".worktrees" / "fast" + self.wf("start", "t-three", "--worktree", str(wt), "--branch", "fast/t-three", project=False, env=IDENT) + (wt / "code.txt").write_text("x\n") + (self.root / "workflow.toml").write_text(SplitCli.toml + 'push = ["exit 1"]\n') + code, out, err = self.wf("finish", "t-three", "-m", "ok", "--commit", "impl", "code.txt", + project=False, cwd=wt, env=IDENT) + self.assertEqual(code, 0, out + err) + code, out, err = self.wf("orch", "post", "t-three", "fast", "--result", "done", project=False, env=IDENT) + self.assertEqual(code, 0, out + err) + self.assertIn("post: t-three push-failed", out) + self.assertIn("stop lane fast: push-failed", out) + self.assertNotIn("branch fast/t-three still there", out) + self.assertIn(f" {self.sha(self.code)} ", (self.root / "out" / "wf-orch.log").read_text()) @@ -1645,8 +1645,9 @@ def lane_worktree(main: Path, p: Project, lane: str, id: str) -> Path: if f.stem != id and r.get("worktree") and other and (other.status or "").startswith("in progress"): busy.add(Path(r["worktree"])) cwds = live_session_cwds() + base = config.code_main(cfg) or main n = 1 - while not worktree_free(wt := main / ".worktrees" / (lane if n == 1 else f"{lane}-{n}"), f"{lane}/{id}", busy, cwds): + while not worktree_free(wt := base / ".worktrees" / (lane if n == 1 else f"{lane}-{n}"), f"{lane}/{id}", busy, cwds): n += 1 return wt @@ -1772,7 +1773,8 @@ def archived_meta(cfg: config.Config, id: str) -> tuple[str | None, str | None]: def orch_post(main: Path, p: Project, args) -> int: cfg, id, lane = p.cfg, args.id, args.lane rec = orch_record(cfg, id) - wt = Path(rec.get("worktree") or main / ".worktrees" / lane) + repo = config.code_main(cfg) or main + wt = Path(rec.get("worktree") or repo / ".worktrees" / lane) item = p.doc.item(id) if id in p.doc.ids() else None if item is None and id not in p.archived: raise Failure(tasks.unknown_id(id, p.doc.ids())) @@ -1780,7 +1782,7 @@ def orch_post(main: Path, p: Project, args) -> int: outcome = words[0] if words else ("done" if id in p.archived else "no-report") if outcome == "done" and item is not None and id not in p.archived and tasks.open_slices(p.doc, id): outcome = "sliced" # slice job: worker said done but the task is open with slices - master = config.git_branch(main / ".git") or "master" + master = config.git_branch(repo / ".git") or "master" files = [str(f.relative_to(main)) for f in (cfg.tasks, cfg.archive)] problems, out = [], [] with project_lock(cfg.root): @@ -1800,7 +1802,7 @@ def orch_post(main: Path, p: Project, args) -> int: except Failure as e: problems.append(f"wf merge in {wt}: {e}") branch = rec.get("branch") or f"{lane}/{id}" - if not git_run(main, "rev-parse", "--verify", "-q", f"refs/heads/{branch}").returncode: + if not git_run(repo, "rev-parse", "--verify", "-q", f"refs/heads/{branch}").returncode: problems.append(f"branch {branch} still there") errors = checks.check(config.load(cfg.root))[0] if errors: @@ -1812,11 +1814,13 @@ def orch_post(main: Path, p: Project, args) -> int: raised = bool(item and rec.get("model") and not outcome.startswith("done") and tasks.MODELS.index(item.model) > tasks.MODELS.index(rec["model"])) final = "post-check-red" if problems else ("model-raised" if raised else outcome) + if outcome.startswith("done") and not problems and (cfg.root / ".wf" / "push-failed").is_file(): + final = "push-failed" am, ae = (None, None) if item or (rec.get("model") and rec.get("effort")) else archived_meta(cfg, id) model = rec.get("model") or (item.model if item else am) or "?" dur = args.duration if args.duration else ( # None/0 (orchestrator lacked duration_ms) → since the pick int(datetime.datetime.now().timestamp()) - rec["at"] if isinstance(rec.get("at"), int) else None) - commit = args.commit or (git_run(main, "rev-parse", "--short", master).stdout.strip() + commit = args.commit or (git_run(repo, "rev-parse", "--short", master).stdout.strip() if outcome.startswith("done") else "-") now = datetime.datetime.now().isoformat(timespec="seconds") (cfg.root / "out").mkdir(exist_ok=True) @@ -1841,6 +1845,7 @@ def orch_post(main: Path, p: Project, args) -> int: return 0 if final not in GO_ON and final != "model-raised": print(f"stop lane {lane}: {final} → tell the owner" + + (f" (wf push in {cfg.root})" if final == "push-failed" else "") + (f" (crash/no report: one fresh worker: wf orch pick {lane} --id {id} --recovery \"<why>\", then stop)" if final == "no-report" else "")) return 0 |
