From 0e50e37328da01c2729cde5668b372cc509c31df Mon Sep 17 00:00:00 2001 From: Desi Villanueva <217994822+dezgit2025@users.noreply.github.com> Date: Wed, 30 Sep 2026 18:17:06 -0400 Subject: [PATCH 1/4] ci: add Windows, packaging and newer Python checks Run the suite on Windows and on Linux Python 3.13/3.14, build the wheel and smoke-test every console script and bundled data file from a clean install, and cover Copilot CLI workspace labels with unit tests. --- .github/workflows/test.yml | 77 ++++++++++++++++++- .../tests/test_copilot_cli_labels.py | 51 ++++++++++++ 2 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 src/session_recall/tests/test_copilot_cli_labels.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0c16c13..4efc5c0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,8 +12,9 @@ jobs: name: Portable / Python ${{ matrix.python-version }} runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - python-version: ["3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v6 @@ -35,6 +36,80 @@ jobs: --ignore=src/session_recall/codex_fix/tests/test_v5_sandbox.py --ignore=src/session_recall/codex_fix/tests/test_v5_workflow.py + windows-test: + name: Windows / Python 3.12 + runs-on: windows-latest + + steps: + - uses: actions/checkout@v6 + + - name: Set up Python 3.12 + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install package + run: pip install -e ".[dev]" + + - name: Lint with ruff + run: ruff check src/ + + - name: Run tests + shell: bash + run: >- + pytest src/ -q -W error + --ignore=src/session_recall/codex_fix/tests/test_v5_sandbox.py + --ignore=src/session_recall/codex_fix/tests/test_v5_workflow.py + + package-smoke: + name: Package / wheel install + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Set up Python 3.12 + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Build wheel + run: | + pip install build + python -m build --wheel + + - name: Install wheel into a clean environment + run: | + python -m venv "$RUNNER_TEMP/pkg" + "$RUNNER_TEMP/pkg/bin/pip" install dist/*.whl + + - name: Check console scripts and bundled data + run: | + set -euo pipefail + expected="$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")" + src_pyz="$(ls src/session_recall/codex_fix/data/artifacts/*.pyz | wc -l)" + bin="$RUNNER_TEMP/pkg/bin" + cd "$RUNNER_TEMP" + for cmd in session-recall session-recall-cc session-recall-claude \ + session-recall-codex session-recall-codex-fix; do + "$bin/$cmd" --help > /dev/null + done + test "$("$bin/session-recall" --version)" = "auto-memory $expected" + test "$("$bin/session-recall-codex" --version)" = "$expected" + test "$("$bin/session-recall-codex-fix" --version)" = "$expected" + "$bin/python" - "$src_pyz" <<'EOF' + import sys + from importlib.resources import files + fix = files("session_recall.codex_fix") / "data" + codex = files("session_recall.providers.codex") / "verifications" + for f in (fix / "catalogue-v1.json", fix / "model-policy-v3.json", + codex / "captured-profiles.json"): + assert f.is_file(), f"missing from wheel: {f}" + pyz = [p for p in (fix / "artifacts").iterdir() if p.name.endswith(".pyz")] + assert len(pyz) == int(sys.argv[1]), f"wheel has {len(pyz)} .pyz, source has {sys.argv[1]}" + print(f"package data ok ({len(pyz)} artifacts)") + EOF + v5-stage-c-macos: name: V5 Stage C / macOS ARM64 runs-on: macos-15 diff --git a/src/session_recall/tests/test_copilot_cli_labels.py b/src/session_recall/tests/test_copilot_cli_labels.py new file mode 100644 index 0000000..f3a8b2f --- /dev/null +++ b/src/session_recall/tests/test_copilot_cli_labels.py @@ -0,0 +1,51 @@ +"""Tests for Copilot CLI workspace labels and repo detection.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +import pytest + +from session_recall.providers.copilot_cli import _labels + +posix_only = pytest.mark.skipif( + sys.platform == "win32", reason="POSIX path rendering" +) + + +@pytest.mark.parametrize("value", [None, ""]) +def test_label_empty_path_returns_none(value) -> None: + assert _labels._local_workspace_label(value) is None + + +@posix_only +def test_label_absolute_path_is_unchanged() -> None: + assert _labels._local_workspace_label("/work/app") == "local:/work/app" + + +@posix_only +def test_label_relative_path_keeps_forward_slashes() -> None: + assert _labels._local_workspace_label("projects/app") == "local:projects/app" + + +@posix_only +def test_label_expands_home(monkeypatch, tmp_path: Path) -> None: + monkeypatch.setenv("HOME", str(tmp_path)) + assert _labels._local_workspace_label("~/app") == f"local:{tmp_path}/app" + + +def test_detect_repo_from_directory(monkeypatch, tmp_path: Path) -> None: + monkeypatch.setattr( + _labels, "detect_repo_for_cwd", lambda p: "o/r" if p == str(tmp_path) else None + ) + assert _labels._detect_repo_for_path(str(tmp_path)) == "o/r" + + +def test_detect_repo_from_file_uses_its_directory(monkeypatch, tmp_path: Path) -> None: + target = tmp_path / "notes.md" + target.write_text("x", encoding="utf-8") + monkeypatch.setattr( + _labels, "detect_repo_for_cwd", lambda p: "o/r" if p == str(tmp_path) else None + ) + assert _labels._detect_repo_for_path(str(target)) == "o/r" From 10516d4246391bd823e6cb9297d2a322d9fad09c Mon Sep 17 00:00:00 2001 From: Desi Villanueva <217994822+dezgit2025@users.noreply.github.com> Date: Wed, 30 Sep 2026 18:19:53 -0400 Subject: [PATCH 2/4] ci: scope Windows job to supported commands, enable Claude gate in smoke --- .github/workflows/test.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4efc5c0..358de9b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -54,12 +54,11 @@ jobs: - name: Lint with ruff run: ruff check src/ + # The Codex launcher and fixer are POSIX-only (deploy/install-codex.md); + # native Windows covers recall for Copilot CLI and Claude Code. - name: Run tests shell: bash - run: >- - pytest src/ -q -W error - --ignore=src/session_recall/codex_fix/tests/test_v5_sandbox.py - --ignore=src/session_recall/codex_fix/tests/test_v5_workflow.py + run: pytest src/ -q -W error --ignore=src/session_recall/codex_fix package-smoke: name: Package / wheel install @@ -84,6 +83,8 @@ jobs: "$RUNNER_TEMP/pkg/bin/pip" install dist/*.whl - name: Check console scripts and bundled data + env: + SESSION_RECALL_ENABLE_CLAUDE_BACKEND: "1" run: | set -euo pipefail expected="$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")" From 3f76c6ce614b127b443abdf813953e95db6dd7d6 Mon Sep 17 00:00:00 2001 From: Desi Villanueva <217994822+dezgit2025@users.noreply.github.com> Date: Wed, 30 Sep 2026 18:23:32 -0400 Subject: [PATCH 3/4] test: make e2e runner portable and track Windows path bug Run e2e subprocesses from the system temp dir instead of /tmp, skip the POSIX-only Codex reader on Windows, and mark the two Copilot CLI WSL-path tests as strict expected failures on Windows until #29 lands. --- .github/workflows/test.yml | 5 ++++- src/session_recall/tests/e2e/conftest.py | 5 +++-- .../tests/e2e/test_e2e_health_provider.py | 3 ++- src/session_recall/tests/test_provider_backends.py | 11 +++++++++++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 358de9b..b75db29 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,7 +58,10 @@ jobs: # native Windows covers recall for Copilot CLI and Claude Code. - name: Run tests shell: bash - run: pytest src/ -q -W error --ignore=src/session_recall/codex_fix + run: >- + pytest src/ -q -W error + --ignore=src/session_recall/codex_fix + --ignore=src/session_recall/providers/codex package-smoke: name: Package / wheel install diff --git a/src/session_recall/tests/e2e/conftest.py b/src/session_recall/tests/e2e/conftest.py index 2d1e7f4..1516d48 100644 --- a/src/session_recall/tests/e2e/conftest.py +++ b/src/session_recall/tests/e2e/conftest.py @@ -5,6 +5,7 @@ import sqlite3 import subprocess import sys +import tempfile from datetime import datetime, timedelta, timezone import pytest @@ -244,7 +245,7 @@ def empty_db(tmp_path_factory): def run_cli(*args: str, db_path: str | None = None) -> subprocess.CompletedProcess: """Invoke session-recall as a subprocess with optional DB override. - Runs from /tmp to avoid auto-detection of the current git repo, + Runs from the system temp dir to avoid auto-detection of the current git repo, ensuring the fixture DB's repos are visible without --repo filtering. """ env = os.environ.copy() @@ -257,7 +258,7 @@ def run_cli(*args: str, db_path: str | None = None) -> subprocess.CompletedProce text=True, timeout=30, env=env, - cwd="/tmp", + cwd=tempfile.gettempdir(), ) diff --git a/src/session_recall/tests/e2e/test_e2e_health_provider.py b/src/session_recall/tests/e2e/test_e2e_health_provider.py index bf20f51..d144a8e 100644 --- a/src/session_recall/tests/e2e/test_e2e_health_provider.py +++ b/src/session_recall/tests/e2e/test_e2e_health_provider.py @@ -4,6 +4,7 @@ import os import subprocess import sys +import tempfile def _run_health(*extra_args: str, env_override: dict[str, str] | None = None): @@ -18,7 +19,7 @@ def _run_health(*extra_args: str, env_override: dict[str, str] | None = None): text=True, timeout=30, env=env, - cwd="/tmp", + cwd=tempfile.gettempdir(), ) diff --git a/src/session_recall/tests/test_provider_backends.py b/src/session_recall/tests/test_provider_backends.py index 4bef207..f04e541 100644 --- a/src/session_recall/tests/test_provider_backends.py +++ b/src/session_recall/tests/test_provider_backends.py @@ -3,8 +3,11 @@ from __future__ import annotations import json +import sys from pathlib import Path +import pytest + from session_recall.providers.copilot_cli import CopilotCliProvider from session_recall.providers.file import ( VSCodeProvider, @@ -13,6 +16,12 @@ _is_wsl, ) +# Windows Python rewrites POSIX/WSL session paths (/home/x -> \home\x). +# Fixed by PR #29; strict so CI flags these once the fix lands. +posix_paths_on_windows = pytest.mark.xfail( + sys.platform == "win32", reason="POSIX paths rewritten on Windows (#29)", strict=True +) + def test_vscode_kind1_inputtext_extraction() -> None: row = { @@ -126,6 +135,7 @@ def test_cli_fallback_prefers_context_repository_field(tmp_path: Path) -> None: assert results[0]["repository"] == "dezgit2025/auto-memory" +@posix_paths_on_windows def test_cli_fallback_uses_tool_path_to_infer_repository( monkeypatch, tmp_path: Path ) -> None: @@ -176,6 +186,7 @@ def test_cli_fallback_uses_tool_path_to_infer_repository( assert sessions[0]["repository"] == "dezgit2025/auto-memory" +@posix_paths_on_windows def test_cli_fallback_labels_non_repo_session_as_local_workspace( tmp_path: Path, ) -> None: From ae7b8c93aa04e7f6ad8d2dd4a85a54df2af15f48 Mon Sep 17 00:00:00 2001 From: Desi Villanueva <217994822+dezgit2025@users.noreply.github.com> Date: Wed, 30 Sep 2026 18:25:52 -0400 Subject: [PATCH 4/4] test: mark Windows cp1252 health output crash as expected failure --- src/session_recall/tests/e2e/test_e2e_health_provider.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/session_recall/tests/e2e/test_e2e_health_provider.py b/src/session_recall/tests/e2e/test_e2e_health_provider.py index d144a8e..b899862 100644 --- a/src/session_recall/tests/e2e/test_e2e_health_provider.py +++ b/src/session_recall/tests/e2e/test_e2e_health_provider.py @@ -6,6 +6,8 @@ import sys import tempfile +import pytest + def _run_health(*extra_args: str, env_override: dict[str, str] | None = None): """Run session-recall health as a subprocess, return CompletedProcess.""" @@ -23,6 +25,11 @@ def _run_health(*extra_args: str, env_override: dict[str, str] | None = None): ) +# Human-readable health prints zone emoji; Windows' default cp1252 pipe +# encoding cannot encode them. Strict so CI flags this once it is fixed. +@pytest.mark.xfail( + sys.platform == "win32", reason="health emoji on cp1252 stdout", strict=True +) def test_e2e_health_default_still_works(): result = _run_health() assert result.returncode == 0, f"stderr: {result.stderr}"