diff options
| author | godosa <godosa@godosa.eu> | 2026-10-07 09:40:59 +0200 |
|---|---|---|
| committer | godosa <godosa@godosa.eu> | 2026-10-07 09:40:59 +0200 |
| commit | 0e1e107192882809da7e660c09ba501c09331c38 (patch) | |
| tree | 32b2fd332464ca028c4dc5a8b03a216edeeb3229 | |
| parent | 811de09feb866c5b344034399154bde77acdd5ba (diff) | |
| download | workflow-0e1e107192882809da7e660c09ba501c09331c38.tar.gz workflow-0e1e107192882809da7e660c09ba501c09331c38.zip | |
split projects 6/6: wf check split rules; docs
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-- | docs/manual.md | 7 | ||||
| -rw-r--r-- | docs/multi-session.md | 4 | ||||
| -rw-r--r-- | docs/orchestrator.md | 7 | ||||
| -rw-r--r-- | shared/agents/wf-worker.md | 2 | ||||
| -rw-r--r-- | tests/test_split.py | 18 | ||||
| -rw-r--r-- | wflib/check.py | 25 |
7 files changed, 59 insertions, 5 deletions
@@ -1,4 +1,5 @@ # Changes (newest first) +- 2026-10-07 split projects 6/6: `wf check` split rules (cloud error; warns: no push, `.wf-home` not git-ignored, merged code worktrees); docs. Projects: split projects: push key, .wf-home in the code repo's .gitignore (migration task in the workflow project). - 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. diff --git a/docs/manual.md b/docs/manual.md index 26661b6..c9f7bd1 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -238,3 +238,10 @@ wf add "Cave seams. Close the slit beside the lintel." -p 1 -e '<1h' --model son | A worker or batch does nothing | `wf list --runner` is empty: tasks lack a Done line (`wf batch --prep`), are blocked on an awaiting item, or wait on `After:`. `wf lanes` shows counts per lane. | | `wf check` errors after a hand edit | Read the message (duplicate id, broken `[[link]]`, bad section) and fix the item. Ids are never reused. | | `project format N is newer than this wf` | Update the tool: `git -C /projects/public/workflow pull`. | + +## Split projects (private home repo + public code repo) +`code_root` in workflow.toml points at another git repo: TASKS/archive/specs stay in the private project, code goes to the +code repo. `wf start` makes code worktrees (branch in the code repo, `.wf-home` file pointing back); `wf finish`/`wf merge`/ +`wf wip` commit private paths in the private repo and code paths in the code repo. `push = [cmd…]` runs in the private root +after a merge (env `WF_MAIN`); `wf push` runs it by hand; failure writes `.wf/push-failed`. Add `.wf-home` to the code repo's +`.gitignore`. The cloud lane is refused. `wf check` warns: no `push`, `.wf-home` not ignored, merged leftover code worktrees. diff --git a/docs/multi-session.md b/docs/multi-session.md index 628aad9..225809e 100644 --- a/docs/multi-session.md +++ b/docs/multi-session.md @@ -44,3 +44,7 @@ sessions/workers never lose a TASKS.md edit or race a merge-back. ## 4. Commits Shared rule: stage explicit paths, never `git add -A` / `git commit -a`. + +## Split projects +Code worktrees come from `wf start <id> --worktree <path> --branch <b>` run in the private project: it creates them in the +code repo (`code_root`) and writes `.wf-home` there. Inside such a worktree wf resolves the private project via `.wf-home`. diff --git a/docs/orchestrator.md b/docs/orchestrator.md index 845c527..f878855 100644 --- a/docs/orchestrator.md +++ b/docs/orchestrator.md @@ -35,7 +35,7 @@ The steps they automate: 1. Pick: `wf list --runner --lane <lane>` → first id (lanes: `wf lanes`). Claim: `wf status <id> progress "worker"`. No commit of its own: `wf merge` or any later bookkeeping commit takes TASKS.md (and the claim) along; harmless. -2. Worktree: `<main>/.worktrees/<lane>`; if a live session or another worker uses it, `<lane>-2`, `-3`, … +2. Worktree: `<main>/.worktrees/<lane>` (split project: under the code repo, `<code_root top>/.worktrees/<lane>`); if a live session or another worker uses it, `<lane>-2`, `-3`, … (live interactive sessions: `~/.claude/sessions/*.json` field `cwd`). Branch `<lane>/<id>`. 3. Spawn: Agent tool, `subagent_type: wf-worker`, `model: <task Model, none = opus>`, background, no worktree isolation (it blocks `wf merge` on the main checkout). Prompt: @@ -150,3 +150,8 @@ TASKS.md, the log and the batch summaries. Safe to clear only with no background worker running. Tested (2026-10-05): `/clear` leaves a background subagent alive and its hand-back + completion notice reach the new context, but SIGKILLs its running Bash command (exit 137): a worker mid-test or mid-commit loses that step and reports a false failure or half state. + +## Split projects +`code_root` = another git repo: worktrees and branches live in the code repo, `wf start` makes them (with `.wf-home` +pointing at the private project). `wf orch post` checks the code repo. A done worker whose push (`push` key) failed +leaves `.wf/push-failed`: the lane stops (`push-failed`); fix the push, delete the marker, continue. diff --git a/shared/agents/wf-worker.md b/shared/agents/wf-worker.md index 555b20c..f60e35b 100644 --- a/shared/agents/wf-worker.md +++ b/shared/agents/wf-worker.md @@ -35,7 +35,7 @@ Run the `Verify` commands `wf ctx` printed (long ones via `wf res run`, waited for in the foreground; red → fix or hand back), then ONE call: `wf finish <id> -m "<entry>" --commit "<msg + footer>" <explicit paths>` (in the worktree; no code → no `--commit`) = quick gate → done → commit → `wf merge`; a refusal (gate red, stray uncommitted files) leaves the task open: fix, rerun. -Paths = this worktree's files only (another repo's: commit there first, finish without them); never hand-edit or commit +Paths = this worktree's files only (another repo's: commit there first, finish without them). Split project (code_root = another repo; worktree is in the code repo): private-repo paths (docs, specs) are allowed in `wf finish`/`wf wip` and committed there; the report's commit line is `report: commit <sha> [books <sha>] [tool <sha>] [push-failed N]`; never hand-edit or commit TASKS.md/archive (wf writes the main tree's, `wf merge` commits them). Failed after `done:` → fix, rerun the same `wf finish` (it resumes). Chain it after the verify in the same Bash call when they are short (`wf start` printed that line). A verify step that says background / gate on a commit → run it after `wf finish`, in the foreground, from the main tree (`cd <main>`, so logs land in its `out/`), on the merged sha diff --git a/tests/test_split.py b/tests/test_split.py index 965f667..934bf6f 100644 --- a/tests/test_split.py +++ b/tests/test_split.py @@ -258,3 +258,21 @@ class OrchSplitTest(SplitCli): 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()) + + +class CheckSplitTest(SplitCli): + def test_cloud_error_push_and_ignore_warnings(self): + (self.root / "workflow.toml").write_text(SplitCli.toml + "cloud = true\n") + (self.code / ".gitignore").write_text(".worktrees/\n") + git(self.code, "commit", "-qam", "no wf-home ignore") + code, out, err = self.wf("check") + self.assertEqual(code, 1) + self.assertIn("cloud lane needs a single repo", out) + self.assertIn("split project without push", out) + self.assertIn(".wf-home not git-ignored", out) + + def test_clean_split_project(self): + (self.root / "workflow.toml").write_text(SplitCli.toml + 'push = ["true"]\n') + code, out, err = self.wf("check") + self.assertEqual(code, 0, out) + self.assertNotIn("split", out) diff --git a/wflib/check.py b/wflib/check.py index f86b2fe..c528c43 100644 --- a/wflib/check.py +++ b/wflib/check.py @@ -8,7 +8,7 @@ from dataclasses import dataclass from pathlib import Path from . import areas, refs, tasks -from .config import NAME, Config +from .config import NAME, WF_HOME, Config, git_top NUMBERED_RE = re.compile(r"^\d+\. ") NUMBER_REF_RE = re.compile(r"(?<![\w/&#])#\d+\b") @@ -301,7 +301,7 @@ def _git(root: Path, *args: str) -> str | None: return out.stdout.strip() if out.returncode == 0 else None -def merged_tool_worktrees(tool_root: Path) -> list[Problem]: +def merged_tool_worktrees(tool_root: Path, label: str = "tool") -> list[Problem]: """Worktrees of the wf tool repo whose branch is behind master (merged, leftover after release). A branch equal to master, or a worktree created < 2h ago, is not flagged.""" listing = _git(tool_root, "worktree", "list", "--porcelain") @@ -326,11 +326,27 @@ def merged_tool_worktrees(tool_root: Path) -> list[Problem]: except OSError: pass if _git(tool_root, "merge-base", "--is-ancestor", branch, "master") is not None: - out.append(Problem("wf-tool", None, "", f"tool worktree '{path}' (branch {branch}) is merged into " + out.append(Problem("wf-tool", None, "", f"{label} worktree '{path}' (branch {branch}) is merged into " f"master: git worktree remove it, git branch -d {branch}")) return out +def split_problems(cfg: Config) -> tuple[list[Problem], list[Problem]]: + """Split project rules (code_root = another repo): cloud refused, push expected, .wf-home ignored, merged code worktrees.""" + if not cfg.split: + return [], [] + code = git_top(cfg.code_root) + errors, warnings = [], [] + if cfg.cloud: + errors.append(Problem("workflow.toml", None, "", "cloud lane needs a single repo: split project (code_root is another repo)")) + if not cfg.push: + warnings.append(Problem("workflow.toml", None, "", "split project without push: public repo pushed unscanned or not at all")) + if _git(code, "check-ignore", "-q", WF_HOME) is None: + warnings.append(Problem(f"{code}/.gitignore", None, "", f"{WF_HOME} not git-ignored (wf start writes it into code worktrees)")) + warnings += merged_tool_worktrees(code, label="code") + return errors, warnings + + def _area_problems(cfg: Config) -> list[Problem]: file = cfg.areas_file if not file.is_file(): @@ -371,6 +387,9 @@ def check(cfg: Config, tasks_text: str | None = None, archive_text: str | None = if slow: warnings += _stale_awaiting(cfg, text) warnings += _area_problems(cfg) + e, w = split_problems(cfg) + errors += e + warnings += w order: dict[str, int] = {} for p in errors + warnings: order.setdefault(p.path, len(order)) |
