diff options
| author | godosa <godosa@godosa.eu> | 2026-10-07 09:17:34 +0200 |
|---|---|---|
| committer | godosa <godosa@godosa.eu> | 2026-10-07 09:17:34 +0200 |
| commit | df72f6f16a7a5141ff4df1bab7e1de34207a4163 (patch) | |
| tree | d248d09369f3713c8f47bccb314c5a2eb82a7d40 | |
| parent | 0f6857c26e5b2777c6be927dbc75fdcdbfe0e750 (diff) | |
| download | workflow-df72f6f16a7a5141ff4df1bab7e1de34207a4163.tar.gz workflow-df72f6f16a7a5141ff4df1bab7e1de34207a4163.zip | |
split projects 2/6: wf start makes code worktrees (.wf-home), wf next advice
Co-Authored-By: Claude Opus 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 | 37 | ||||
| -rwxr-xr-x | wf.py | 16 |
3 files changed, 50 insertions, 4 deletions
@@ -1,4 +1,5 @@ # Changes (newest first) +- 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. - 2026-10-07 Shared push rule: in a split project the public `code_root` repo is pushed only via the project's publish command, never `git push home --all` (that pushes local review branches unscanned). Projects: split projects name their publish command in their CLAUDE.md. - 2026-10-07 Denylist guard at commit time: `.githooks/pre-commit` (staged lines + paths) and `.githooks/commit-msg` run `scripts/denylist_check.py` against the git-ignored `publish-denylist.local` (worktree top + main tree, plus `git config denylist.file` lists; `word` / `!token` exemption / `#`, case-insensitive; output `word #<line>` only; no list → warning). Enable per clone: `git config core.hooksPath .githooks`; audit: `denylist_check.py tree`. `scripts/publish_snapshot.py` removed (history is public now). Projects: nothing. diff --git a/tests/test_split.py b/tests/test_split.py index 2026e33..346c138 100644 --- a/tests/test_split.py +++ b/tests/test_split.py @@ -1,3 +1,4 @@ +import os import subprocess import unittest from pathlib import Path @@ -72,3 +73,39 @@ class DiscoveryTest(SplitCli): self.assertFalse(cfg.split) self.assertIsNone(config.code_main(cfg)) self.assertEqual(cfg.push, ["true"]) + + +class StartSplitTest(SplitCli): + toml = SplitCli.toml + 'worktree_setup = ["pwd > where.txt"]\n' + + def test_start_makes_code_worktree_with_wf_home(self): + wt = self.code / ".worktrees" / "slow" + code, out, err = self.wf("start", "t-three", "--worktree", str(wt), "--branch", "slow/t-three", + project=False, env=IDENT) + self.assertEqual(code, 0, out + err) + self.assertEqual((wt / ".wf-home").read_text(), f"{self.root}\n") + self.assertEqual(subprocess.run(["git", "branch", "--show-current"], cwd=wt, capture_output=True, + text=True).stdout.strip(), "slow/t-three") + self.assertEqual((wt / "where.txt").read_text().strip(), str(wt)) # setup cwd = code worktree + self.assertIn("(in progress: slow/t-three)", (self.root / "TASKS.md").read_text()) + self.assertIn(f"cd {wt} && (make test) && ", out) + self.assertFalse((self.root / ".worktrees").exists()) # nothing in the private repo + + def test_setup_in_code_worktree(self): + wt = self.add_wt() + code, out, err = self.wf("setup", project=False, cwd=wt) + self.assertEqual(code, 0, err) + self.assertEqual((wt / "where.txt").read_text().strip(), str(wt)) + + def env(self, name, pid): # copy of tests/test_claims.py ClaimTest.env + sock = self.root / f"{name}.sock" + sock.write_text("") + return {"CLAUDE_CODE_MESSAGING_SOCKET": str(sock), "CLAUDE_PID": str(pid), "CLAUDE_CODE_SESSION_ID": name} + + def test_next_advice_names_wf_start_in_code_repo(self): + # pattern: tests/test_claims.py::test_next_says_worktree_mode_when_two_sessions_live + son, me = self.env("s", os.getppid()), self.env("o", os.getpid()) + self.wf("next", "--lane", "slow", "--as", "sonnet", "--brief", env=son) + code, out, err = self.wf("next", "--lane", "fast", "--as", "opus", env=me) + self.assertIn(f" wf start <task> --worktree {self.code / '.worktrees' / 'fast'} --branch fast/<task> " + f"(run in {self.root}; wf there writes this TASKS.md)\n", out) @@ -392,6 +392,11 @@ def multi_lines(p: Project, args, lane: str, extra: int) -> list[str]: if wt: return [head + f"you are in worktree {p.cfg.rel(wt[0])} (branch {config.git_branch(wt[1])}); " "wf writes the main tree's TASKS.md"] + if code := config.code_main(p.cfg): + master = config.git_branch(code / ".git") or "master" + cmd = f"wf start <task> --worktree {code / '.worktrees' / lane} --branch {lane}/<task>" + return [head + f"work in your lane's code worktree, never on {master}:", + f" {cmd} (run in {p.cfg.root}; wf there writes this TASKS.md)"] folder, master = p.cfg.root / ".worktrees" / lane, config.git_branch(main_top / ".git") or "master" cmd = (f"cd {p.cfg.rel(folder)} && git switch -c {lane}/<task> {master}" if folder.is_dir() else f"git worktree add {p.cfg.rel(folder)} -b {lane}/<task> {master}") @@ -1424,7 +1429,8 @@ def cmd_start(args) -> int: p = load_project(args) id = p.resolve(args.id) main = Path(git_run(start, "rev-parse", "--show-toplevel").stdout.strip() or p.cfg.root) - master = config.git_branch(main / ".git") or "master" + repo = config.code_main(p.cfg) or main # split project: worktree + branch in the code repo + master = config.git_branch(repo / ".git") or "master" wt = (Path.cwd() / args.worktree).resolve() shown = p.cfg.rel(wt) b = args.branch @@ -1435,14 +1441,14 @@ def cmd_start(args) -> int: raise Failure(f"git {a[0]} failed: {(r.stderr.strip() or r.stdout.strip() or 'git error').splitlines()[-1]}") return r.stdout - has_branch = not git_run(main, "rev-parse", "--verify", "-q", f"refs/heads/{b}").returncode + has_branch = not git_run(repo, "rev-parse", "--verify", "-q", f"refs/heads/{b}").returncode extra = [] if not wt.exists(): if has_branch: - git_ok(main, "worktree", "add", "-q", str(wt), b) + git_ok(repo, "worktree", "add", "-q", str(wt), b) how = f"new, existing branch {b}: earlier WIP, read the notes" else: - git_ok(main, "worktree", "add", "-q", str(wt), "-b", b, master) + git_ok(repo, "worktree", "add", "-q", str(wt), "-b", b, master) how = f"new, branch {b} from {master}" else: dirty = git_run(wt, "status", "--porcelain").stdout.rstrip("\n") @@ -1467,6 +1473,8 @@ def cmd_start(args) -> int: extra = [f"recovery: uncommitted:\n{dirty}" if dirty else "recovery: uncommitted: none", f"recovery: commits {master}..HEAD:" + (f"\n{log}" if log else " none")] print(f"worktree: {shown} ({how})", *extra, sep="\n", flush=True) + if repo != main: + (wt / config.WF_HOME).write_text(f"{p.cfg.root}\n") try: here = wt / p.cfg.root.relative_to(main) except ValueError: |
