diff --git a/.agents/skills/lib_py/loop/runner.py b/.agents/skills/lib_py/loop/runner.py index 8de3938..c5d7726 100644 --- a/.agents/skills/lib_py/loop/runner.py +++ b/.agents/skills/lib_py/loop/runner.py @@ -256,7 +256,12 @@ class LoopRunner: print("Use --plan to have the Planner write a new plan, or --plan-file to reuse one:", file=sys.stderr) print(' run_loop.sh --creator --plan-file --task "..."', file=sys.stderr) sys.exit(1) - resolved = Path(self.plan_file).expanduser().resolve() + # A relative path is taken against the control root, as the usage + # line says and as jobs_dir already does (Issue #21 R2): the runner + # never chdirs to workspace_root, so resolving against the caller's + # cwd would look somewhere else entirely - and could find a + # same-named file there. `/` keeps an absolute path as it is. + resolved = (self.workspace_root / Path(self.plan_file).expanduser()).resolve() text = self._plan_text(resolved) if text is None: log_error(f"--plan-file {resolved}: not a readable file with plan text in it.") diff --git a/tests/test_issue25_plan_reuse.py b/tests/test_issue25_plan_reuse.py index 175876f..5296967 100644 --- a/tests/test_issue25_plan_reuse.py +++ b/tests/test_issue25_plan_reuse.py @@ -203,6 +203,48 @@ def test_p3b_the_refusal_names_the_resolved_path(ctl, tmp_path, capsys): assert str(missing.resolve()) in capsys.readouterr().err +def test_p3c_a_relative_plan_file_is_taken_against_the_control_root(ctl, tmp_path, monkeypatch): + """R1/N1: the usage line promises control-root-relative, and the runner + never chdirs to the control root. Resolving against the caller's cwd looked + somewhere else - and a same-named file there won silently, which is the + very failure #25 is about.""" + (ctl / "plans").mkdir() + (ctl / "plans" / "named.md").write_text(NEW_PLAN, encoding="utf-8") + caller = tmp_path / "caller" + (caller / "plans").mkdir(parents=True) + monkeypatch.chdir(caller) + + r = _runner("--plan-file", "plans/named.md") + r.parse_args() + assert Path(r.plan_file) == (ctl / "plans" / "named.md").resolve() + assert "NAMED-PLAN-MARKER" in r.current_plan + + # the same name in the caller's cwd must not win + (caller / "plans" / "named.md").write_text(OLD_PLAN, encoding="utf-8") + r = _runner("--plan-file", "plans/named.md") + r.parse_args() + assert Path(r.plan_file) == (ctl / "plans" / "named.md").resolve() + assert "NAMED-PLAN-MARKER" in r.current_plan + assert "OLD-PLAN-MARKER" not in r.current_plan + + +def test_p3d_an_absolute_plan_file_is_left_alone(ctl, tmp_path, monkeypatch): + """Anchoring the relative case must not rewrite absolute or ~ paths.""" + named = tmp_path / "abs-plan.md" + named.write_text(NEW_PLAN, encoding="utf-8") + monkeypatch.chdir(tmp_path) + r = _runner("--plan-file", str(named)) + r.parse_args() + assert Path(r.plan_file) == named.resolve() + + monkeypatch.setenv("HOME", str(tmp_path)) + home_plan = tmp_path / "home-plan.md" + home_plan.write_text(NEW_PLAN, encoding="utf-8") + r = _runner("--plan-file", "~/home-plan.md") + r.parse_args() + assert Path(r.plan_file) == home_plan.resolve() + + def test_p4_plan_file_conflicts_with_plan_and_needs_a_value(ctl, tmp_path, monkeypatch): named = tmp_path / "my-plan.md" named.write_text(NEW_PLAN, encoding="utf-8")