From 54f881848bf320da17c5399c65789256eaf49543 Mon Sep 17 00:00:00 2001 From: Vatche Isahagian Date: Mon, 4 May 2026 16:58:29 -0400 Subject: [PATCH] =?UTF-8?q?Revert=20"fix(learn):=20resolve=20saved-traject?= =?UTF-8?q?ory=20path=20via=20shared=20get=5Ftrajectories=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit a584af621cd19aafdf09955c73f4215e0443119c. --- .../plugins/evolve-lite/lib/entity_io.py | 11 -- .../plugins/evolve-lite/skills/learn/SKILL.md | 2 +- .../skills/learn/scripts/on_stop.py | 5 +- .../skills/save-trajectory/scripts/on_stop.py | 17 +- .../test_stop_hooks_path_resolution.py | 152 ------------------ 5 files changed, 16 insertions(+), 171 deletions(-) delete mode 100644 tests/platform_integrations/test_stop_hooks_path_resolution.py diff --git a/platform-integrations/claude/plugins/evolve-lite/lib/entity_io.py b/platform-integrations/claude/plugins/evolve-lite/lib/entity_io.py index a73e2e1f..b8e0eefa 100644 --- a/platform-integrations/claude/plugins/evolve-lite/lib/entity_io.py +++ b/platform-integrations/claude/plugins/evolve-lite/lib/entity_io.py @@ -97,17 +97,6 @@ def get_default_entities_dir(): return base.resolve() -def get_trajectories_dir(): - """Return (and create) the trajectories directory as an absolute Path. - - Uses :func:`get_evolve_dir` for the base so trajectories land alongside - entities under the same ``EVOLVE_DIR`` / ``.evolve`` root. - """ - base = get_evolve_dir() / "trajectories" - base.mkdir(parents=True, exist_ok=True, mode=0o700) - return base.resolve() - - # --------------------------------------------------------------------------- # Slugify / filename helpers # --------------------------------------------------------------------------- diff --git a/platform-integrations/claude/plugins/evolve-lite/skills/learn/SKILL.md b/platform-integrations/claude/plugins/evolve-lite/skills/learn/SKILL.md index e8333ed6..21e70613 100644 --- a/platform-integrations/claude/plugins/evolve-lite/skills/learn/SKILL.md +++ b/platform-integrations/claude/plugins/evolve-lite/skills/learn/SKILL.md @@ -29,7 +29,7 @@ This skill analyzes the current conversation to extract guidelines that **correc This skill runs in a forked context with no access to the parent conversation. The stop-hook message (produced by `on_stop.py`) contains one literal marker: -- `The saved trajectory path is: ` — a copy of the session transcript written by the save-trajectory Stop hook. The path is absolute and resolves under `$EVOLVE_DIR/trajectories/` (or the project's `.evolve/trajectories/` when `EVOLVE_DIR` is unset), with filename `claude-transcript_.jsonl`. Take everything after the colon, strip surrounding whitespace and quotes, and use the result as `saved_trajectory_path`. You will also attach this exact path to each entity's `trajectory` field in Step 4. +- `The saved trajectory path is: ` — a copy of the session transcript saved inside the project tree at `.evolve/trajectories/claude-transcript_.jsonl`. Take everything after the colon, strip surrounding whitespace and quotes, and use the result as `saved_trajectory_path`. You will also attach this exact path to each entity's `trajectory` field in Step 4. **Read this file with the `Read` tool — do NOT shell out.** `Read` pages large files natively (use its `offset` / `limit` parameters if needed). Do not use `cat`, `head`, `wc`, `find`, or `python3 -c` loops on the transcript — those trigger a permission prompt for every invocation and are unnecessary. diff --git a/platform-integrations/claude/plugins/evolve-lite/skills/learn/scripts/on_stop.py b/platform-integrations/claude/plugins/evolve-lite/skills/learn/scripts/on_stop.py index 58e250e8..d26afbcb 100644 --- a/platform-integrations/claude/plugins/evolve-lite/skills/learn/scripts/on_stop.py +++ b/platform-integrations/claude/plugins/evolve-lite/skills/learn/scripts/on_stop.py @@ -5,9 +5,6 @@ import sys from pathlib import Path -sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent.parent / "lib")) -from entity_io import get_trajectories_dir # noqa: E402 - def main(): try: @@ -23,7 +20,7 @@ def main(): if transcript_path: session_id = Path(transcript_path).stem.removeprefix("claude-transcript_") if session_id: - saved_trajectory = str(get_trajectories_dir() / f"claude-transcript_{session_id}.jsonl") + saved_trajectory = f".evolve/trajectories/claude-transcript_{session_id}.jsonl" reason += f" The saved trajectory path is: {saved_trajectory}" print( diff --git a/platform-integrations/claude/plugins/evolve-lite/skills/save-trajectory/scripts/on_stop.py b/platform-integrations/claude/plugins/evolve-lite/skills/save-trajectory/scripts/on_stop.py index 0dcc4ad3..81c3400e 100644 --- a/platform-integrations/claude/plugins/evolve-lite/skills/save-trajectory/scripts/on_stop.py +++ b/platform-integrations/claude/plugins/evolve-lite/skills/save-trajectory/scripts/on_stop.py @@ -10,9 +10,6 @@ import tempfile from pathlib import Path -sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent.parent / "lib")) -from entity_io import get_trajectories_dir # noqa: E402 - _log_file = None @@ -41,6 +38,20 @@ def log(message): pass +def get_trajectories_dir(): + evolve_dir = os.environ.get("EVOLVE_DIR") + if evolve_dir: + base = Path(evolve_dir) / "trajectories" + else: + project_root = os.environ.get("CLAUDE_PROJECT_ROOT", "") + if project_root: + base = Path(project_root) / ".evolve" / "trajectories" + else: + base = Path(".evolve") / "trajectories" + base.mkdir(parents=True, exist_ok=True, mode=0o700) + return base.resolve() + + def main(): try: input_data = json.load(sys.stdin) diff --git a/tests/platform_integrations/test_stop_hooks_path_resolution.py b/tests/platform_integrations/test_stop_hooks_path_resolution.py deleted file mode 100644 index 9b6cb35d..00000000 --- a/tests/platform_integrations/test_stop_hooks_path_resolution.py +++ /dev/null @@ -1,152 +0,0 @@ -"""Tests that both Stop hooks agree on where the saved transcript lives. - -Issue #246: ``learn/scripts/on_stop.py`` used to hardcode -``.evolve/trajectories/...`` while ``save-trajectory/scripts/on_stop.py`` -resolved the path from env. When ``EVOLVE_DIR`` was set the two hooks -disagreed and the learn skill read a non-existent file. - -These tests invoke each hook as a subprocess with a synthetic stdin payload -and assert the path emitted (save-trajectory prints it on stdout; learn -embeds it in the ``reason`` field) matches the shared resolver for the -``EVOLVE_DIR``-set and default scenarios. -""" - -import json -import os -import subprocess -import sys -from pathlib import Path - -import pytest - -pytestmark = pytest.mark.platform_integrations - -_REPO_ROOT = Path(__file__).parent.parent.parent -_PLUGIN_ROOT = _REPO_ROOT / "platform-integrations/claude/plugins/evolve-lite" -LEARN_ON_STOP = _PLUGIN_ROOT / "skills/learn/scripts/on_stop.py" -SAVE_TRAJ_ON_STOP = _PLUGIN_ROOT / "skills/save-trajectory/scripts/on_stop.py" - -_SESSION_ID = "abc-123" -_EXPECTED_FILENAME = f"claude-transcript_{_SESSION_ID}.jsonl" - - -def _run_hook(script, cwd, env_overrides, transcript_path): - """Run a Stop hook with a synthetic stdin payload and return CompletedProcess.""" - env = {k: v for k, v in os.environ.items() if k not in {"EVOLVE_DIR", "CLAUDE_PROJECT_ROOT"}} - env.update(env_overrides) - payload = {"transcript_path": str(transcript_path), "stop_hook_active": False} - return subprocess.run( - [sys.executable, str(script)], - input=json.dumps(payload), - capture_output=True, - text=True, - cwd=str(cwd), - env=env, - check=True, - ) - - -def _learn_reason_path(stdout): - """Extract the path from the learn hook's `reason` field.""" - data = json.loads(stdout) - reason = data["reason"] - marker = "The saved trajectory path is: " - assert marker in reason, f"marker missing in reason: {reason!r}" - return reason.split(marker, 1)[1].strip() - - -def _save_trajectory_path(stdout): - """save-trajectory prints `Trajectory saved: ` on stdout.""" - line = stdout.strip().splitlines()[-1] - prefix = "Trajectory saved: " - assert line.startswith(prefix), f"unexpected stdout: {stdout!r}" - return line[len(prefix) :] - - -@pytest.fixture -def fake_transcript(tmp_path): - """Create a fake live transcript file matching Claude Code's layout.""" - src = tmp_path / "projects" / "fake-project" / f"{_SESSION_ID}.jsonl" - src.parent.mkdir(parents=True) - src.write_text('{"type":"assistant","content":"hi"}\n') - return src - - -# --------------------------------------------------------------------------- -# learn/on_stop.py — emits the resolved path in its `reason` field -# --------------------------------------------------------------------------- - - -def test_learn_uses_evolve_dir(tmp_path, fake_transcript): - custom = tmp_path / "my-evolve" - result = _run_hook( - LEARN_ON_STOP, - cwd=tmp_path, - env_overrides={"EVOLVE_DIR": str(custom)}, - transcript_path=fake_transcript, - ) - path = _learn_reason_path(result.stdout) - assert path == str((custom / "trajectories" / _EXPECTED_FILENAME).resolve()) - - -def test_learn_defaults_to_cwd_evolve(tmp_path, fake_transcript): - result = _run_hook( - LEARN_ON_STOP, - cwd=tmp_path, - env_overrides={}, - transcript_path=fake_transcript, - ) - path = _learn_reason_path(result.stdout) - expected = (tmp_path / ".evolve" / "trajectories" / _EXPECTED_FILENAME).resolve() - assert path == str(expected) - - -# --------------------------------------------------------------------------- -# save-trajectory/on_stop.py — still resolves to the same locations -# --------------------------------------------------------------------------- - - -def test_save_trajectory_uses_evolve_dir(tmp_path, fake_transcript): - custom = tmp_path / "my-evolve" - result = _run_hook( - SAVE_TRAJ_ON_STOP, - cwd=tmp_path, - env_overrides={"EVOLVE_DIR": str(custom)}, - transcript_path=fake_transcript, - ) - written = Path(_save_trajectory_path(result.stdout)) - assert written == (custom / "trajectories" / _EXPECTED_FILENAME).resolve() - assert written.is_file() - - -def test_save_trajectory_defaults_to_cwd_evolve(tmp_path, fake_transcript): - result = _run_hook( - SAVE_TRAJ_ON_STOP, - cwd=tmp_path, - env_overrides={}, - transcript_path=fake_transcript, - ) - written = Path(_save_trajectory_path(result.stdout)) - assert written == (tmp_path / ".evolve" / "trajectories" / _EXPECTED_FILENAME).resolve() - assert written.is_file() - - -# --------------------------------------------------------------------------- -# Parity — the two hooks must agree on the same path for the same session -# --------------------------------------------------------------------------- - - -@pytest.mark.parametrize( - "env_fn", - [ - pytest.param(lambda tmp: {"EVOLVE_DIR": str(tmp / "my-evolve")}, id="evolve-dir"), - pytest.param(lambda tmp: {}, id="default"), - ], -) -def test_hooks_agree_on_path(tmp_path, fake_transcript, env_fn): - env = env_fn(tmp_path) - save_result = _run_hook(SAVE_TRAJ_ON_STOP, tmp_path, env, fake_transcript) - learn_result = _run_hook(LEARN_ON_STOP, tmp_path, env, fake_transcript) - written = _save_trajectory_path(save_result.stdout) - announced = _learn_reason_path(learn_result.stdout) - assert written == announced