diff options
| author | godosa <godosa@godosa.eu> | 2026-10-07 09:31:12 +0200 |
|---|---|---|
| committer | godosa <godosa@godosa.eu> | 2026-10-07 09:31:12 +0200 |
| commit | 4743282cde2cd3d6ec767e93564b2121c6d643e4 (patch) | |
| tree | dc7d8af1a52c75e47dc5ea4c6808607c336a5110 | |
| parent | ecf39ec27307f36d9458a22a332c8848bffe79a5 (diff) | |
| download | workflow-4743282cde2cd3d6ec767e93564b2121c6d643e4.tar.gz workflow-4743282cde2cd3d6ec767e93564b2121c6d643e4.zip | |
split projects 4/6: workflow.toml push, wf push, .wf/push-failed marker
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 | 36 | ||||
| -rwxr-xr-x | wf.py | 52 |
3 files changed, 84 insertions, 5 deletions
@@ -1,4 +1,5 @@ # Changes (newest first) +- 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. - 2026-10-07 split projects 1/6: Config.split, push key, .wf-home discovery. Projects: nothing. diff --git a/tests/test_split.py b/tests/test_split.py index d382a9f..4182ce4 100644 --- a/tests/test_split.py +++ b/tests/test_split.py @@ -195,3 +195,39 @@ class FinishSplitTest(SplitCli): code, out, err = self.wf("done", "t-three", "-m", "ok", project=False, cwd=self.wt, env=IDENT) self.assertEqual(code, 0, out + err) self.assertIn("commit TASKS.md tasks/archive.md, push home", out) + + +class PushSplitTest(SplitCli): + toml = SplitCli.toml + 'push = ["echo pushing", "echo boom; exit 3"]\n' + + def setUp(self): + super().setUp() + self.wt = self.add_wt() + (self.wt / "code.txt").write_text("x\n") + + def test_push_failure_keeps_merge_marks_and_reports(self): + code, out, err = self.wf("finish", "t-three", "-m", "ok", "--commit", "impl", "code.txt", + project=False, cwd=self.wt, env=IDENT) + self.assertEqual(code, 0, out + err) + self.assertIn("not pushed (exit 3): echo boom; exit 3\n", out) + self.assertIn(" boom\n", out) + self.assertIn("rerun: wf push", out) + self.assertRegex(out, r"report: commit [0-9a-f]+ books [0-9a-f]+ push-failed 3\n$") + self.assertEqual(self.log(self.code), "impl\ninit\n") + self.assertTrue((self.root / ".wf" / "push-failed").is_file()) + code, out, err = self.wf("next", "--brief", project=False, cwd=self.root) + self.assertIn("push failed", out) + + def test_wf_push_success_clears_marker(self): + self.wf("finish", "t-three", "-m", "ok", "--commit", "impl", "code.txt", project=False, cwd=self.wt, env=IDENT) + (self.root / "workflow.toml").write_text(SplitCli.toml + 'push = ["true"]\n') + code, out, err = self.wf("push", project=False, cwd=self.root) + self.assertEqual(code, 0, out + err) + self.assertFalse((self.root / ".wf" / "push-failed").exists()) + + def test_push_runs_in_private_root(self): + (self.root / "workflow.toml").write_text(SplitCli.toml + 'push = ["pwd > pushed-from.txt"]\n') + code, out, err = self.wf("finish", "t-three", "-m", "ok", "--commit", "impl", "code.txt", + 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)) @@ -7,6 +7,7 @@ Change: add "Title. Goal." -p N -e EFFORT · done ID… -m "entry" · prio ID N move ID SECTION | --before/--after ID · status ID progress NOTE|blocked A-ID|clear set ID --title/--effort/--after/--ref/--model/--sessions/--cloud · note ID "line" body ID <stdin (Model:/Sessions:/After:/Ref: kept) · tick ID N|TEXT · setup · merge (in a lane worktree) + push (rerun a merge's push: workflow.toml push, else push home) Orch: orch pick LANE [--id ID] [--recovery WHY] · orch post ID LANE [--result LINE] [--agent A] [--duration S] [--no-next] Other: report "what happened" · init · migrate [--write] · res … (shared memory/CPU ledger; wf res -h) batch N [--lanes L,…] [--prep] | --status (unattended batch orchestrator; wf batch -h) @@ -432,6 +433,8 @@ def cmd_next(args) -> int: item, skipped = lanes.pick(p.doc, p.archived, p.cfg.lanes, p.cfg.slice_above, lane, model, claims, other_live(p.cfg), args.owner) multi = multi_lines(p, args, lane or "all", 1 if warning else 0) + if (marker := p.cfg.root / ".wf" / "push-failed").is_file(): + out += [f"push failed: {marker.read_text().splitlines()[0]} → wf push (in {p.cfg.root})"] if not args.brief: awaiting = p.doc.section("awaiting").items if awaiting: @@ -1268,13 +1271,44 @@ def cmd_merge(args) -> int: git_run(top, "switch", "-q", "--detach", master) if branch: git_run(top, "branch", "-q", "-d", branch) - if not args.no_push and any([_push_home(main), bmain != main and _push_home(bmain)]): - out.append("pushed home") + args.push_rc = 0 + if not args.no_push: + args.push_rc, lines = run_push(cfg, [bmain, main]) + out += lines out.append(f"merged {branch or 'detached HEAD'} into {master}") print("\n".join(out)) return 0 +def run_push(cfg: config.Config, repos: list[Path]) -> tuple[int, list[str]]: + """Push after a merge: workflow.toml push lines (cwd = root, env WF_MAIN; first nonzero stops and writes + .wf/push-failed), else _push_home on each repo. (exit code, lines to print).""" + marker = cfg.root / ".wf" / "push-failed" + if not cfg.push: + pushed = [r for r in dict.fromkeys(repos) if _push_home(r)] + return 0, ["pushed home"] if pushed else [] + env = {**os.environ, "WF_MAIN": str(cfg.root)} + for line in cfg.push: + r = subprocess.run(line, shell=True, cwd=cfg.root, env=env, capture_output=True, text=True) + if r.returncode: + tail = (r.stdout + r.stderr).strip().splitlines()[-5:] + wf_folder(cfg, "orch") # creates .wf with its .gitignore + marker.write_text(f"{line}\nexit {r.returncode}\n" + "\n".join(tail) + "\n") + return r.returncode, [f"not pushed (exit {r.returncode}): {line}", *(f" {t}" for t in tail), + "rerun: wf push"] + marker.unlink(missing_ok=True) + return 0, [f"pushed ({len(cfg.push)} push command{'s' * (len(cfg.push) != 1)})"] + + +def cmd_push(args) -> int: + """Run the push of a merge again (workflow.toml push, else push home), from the project or a code worktree.""" + cfg = config.load_at(Path(args.project or Path.cwd()).resolve()) + repos = [r for r in (config.git_top(cfg.root), config.code_main(cfg)) if r] + rc, lines = run_push(cfg, repos) + print("\n".join(lines) or "nothing to push (no push key, no remote home)") + return rc + + def _push_home(main: Path) -> bool: """git push home --all/--tags when remote home exists; True if pushed.""" if "home" not in git_run(main, "remote").stdout.split(): @@ -1371,8 +1405,11 @@ def cmd_finish(args) -> int: sha = git_run(top, "rev-parse", "--short", "HEAD").stdout.strip() tool = getattr(args, "tool_commit", None) print(f"report: commit {sha}" + (f" tool {tool}" if tool else ""), flush=True) - if not wt and not args.no_push and _push_home(top): - print("pushed home", flush=True) + push_rc = 0 + if not wt and not args.no_push: + push_rc, lines = run_push(cfg, [top]) + if lines: + print("\n".join(lines), flush=True) if wt: ns = argparse.Namespace(project=args.project, m=None, no_push=args.no_push, private=priv, private_msg=args.commit) @@ -1380,7 +1417,9 @@ def cmd_finish(args) -> int: if not rc and getattr(ns, "merged_sha", ""): tool = getattr(args, "tool_commit", None) books_sha = f" books {ns.books_sha}" if split else "" - print(f"report: commit {ns.merged_sha}{books_sha}" + (f" tool {tool}" if tool else ""), flush=True) + failed = f" push-failed {ns.push_rc}" if getattr(ns, "push_rc", 0) else "" + print(f"report: commit {ns.merged_sha}{books_sha}" + (f" tool {tool}" if tool else "") + failed, + flush=True) return rc return 0 @@ -2178,6 +2217,9 @@ def parser() -> argparse.ArgumentParser: sp.add_argument("-m", help="bookkeeping commit message (default '<id> done', id from the branch)") sp.add_argument("--no-push", action="store_true", help="skip git push home") + sp = cmd("push", cmd_push, "rerun a merge's push: workflow.toml push lines, else git push home") + sp.set_defaults(locks=True) + sp = cmd("finish", cmd_finish, "worker's last step in one call: quick_gate → done → commit PATHS (explicit) → " "wf merge (lane worktree; main tree: TASKS/archive go into the commit). Run verify first. " "Refuses before done if the gate is red, files outside PATHS are uncommitted, or a PATH is outside " |
