aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/wf.py
diff options
context:
space:
mode:
authorgodosa <godosa@godosa.eu>2026-10-07 09:31:12 +0200
committergodosa <godosa@godosa.eu>2026-10-07 09:31:12 +0200
commit4743282cde2cd3d6ec767e93564b2121c6d643e4 (patch)
treedc7d8af1a52c75e47dc5ea4c6808607c336a5110 /wf.py
parentecf39ec27307f36d9458a22a332c8848bffe79a5 (diff)
downloadworkflow-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
Diffstat (limited to 'wf.py')
-rwxr-xr-xwf.py52
1 files changed, 47 insertions, 5 deletions
diff --git a/wf.py b/wf.py
index f3b7f28..f166208 100755
--- a/wf.py
+++ b/wf.py
@@ -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 "