fix: add UTF-8 encoding configuration for Windows across all entry points

Fixes Unicode encoding errors on Windows by adding encoding configuration
to all entry-point scripts and modules that output Unicode characters.

Changes:
1. Added Windows UTF-8 encoding configuration to all entry-point scripts
   in apps/backend/ that can be run directly or output Unicode content
2. Added encoding="utf-8", errors="replace" to subprocess calls in tests
3. Added encoding configuration to test conftest.py for subprocess safety

This follows the same pattern from run.py to handle Unicode characters
on Windows platforms where the default console encoding is 'charmap'.

Fixes test failures:
- tests/spec/test_validate_spec.py::TestMainCli::test_main_with_all_checkpoints_default
- tests/test_commit_message.py::TestEdgeCases::test_unicode_in_description

Files modified:
- Entry points: commit_message.py, context/main.py, memory/main.py, etc.
- Runners: ai_analyzer_runner.py, ideation_runner.py, insights_runner.py, etc.
- Tests: test_validate_spec.py, test_commit_message.py, conftest.py
This commit is contained in:
StillKnotKnown
2026-02-09 12:31:13 +02:00
parent fd269fd585
commit b9b11d4e54
30 changed files with 605 additions and 7 deletions
+29
View File
@@ -8,9 +8,38 @@ This script verifies that:
3. Backwards compatibility is maintained
"""
import io
import sys
from pathlib import Path
# Configure safe encoding on Windows to handle Unicode characters in output
if sys.platform == "win32":
for _stream_name in ("stdout", "stderr"):
_stream = getattr(sys, _stream_name)
# Method 1: Try reconfigure (works for TTY)
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
continue
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Method 2: Wrap with TextIOWrapper for piped output
try:
if hasattr(_stream, "buffer"):
_new_stream = io.TextIOWrapper(
_stream.buffer,
encoding="utf-8",
errors="replace",
line_buffering=True,
)
setattr(sys, _stream_name, _new_stream)
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Clean up temporary variables
del _stream_name, _stream
if "_new_stream" in dir():
del _new_stream
# Add parent directory to path
sys.path.insert(0, str(Path(__file__).parent.parent))
+30
View File
@@ -29,9 +29,39 @@ All actual implementation is in focused submodules for better maintainability.
from __future__ import annotations
import io
import json
import sys
from pathlib import Path
# Configure safe encoding on Windows to handle Unicode characters in output
if sys.platform == "win32":
for _stream_name in ("stdout", "stderr"):
_stream = getattr(sys, _stream_name)
# Method 1: Try reconfigure (works for TTY)
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
continue
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Method 2: Wrap with TextIOWrapper for piped output
try:
if hasattr(_stream, "buffer"):
_new_stream = io.TextIOWrapper(
_stream.buffer,
encoding="utf-8",
errors="replace",
line_buffering=True,
)
setattr(sys, _stream_name, _new_stream)
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Clean up temporary variables
del _stream_name, _stream
if "_new_stream" in dir():
del _new_stream
# Import from the new modular structure
from .analyzers import (
ProjectAnalyzer,
+29
View File
@@ -12,11 +12,40 @@ Usage:
"""
import argparse
import io
import json
import sys
import uuid
from pathlib import Path
# Configure safe encoding on Windows to handle Unicode characters in output
if sys.platform == "win32":
for _stream_name in ("stdout", "stderr"):
_stream = getattr(sys, _stream_name)
# Method 1: Try reconfigure (works for TTY)
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
continue
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Method 2: Wrap with TextIOWrapper for piped output
try:
if hasattr(_stream, "buffer"):
_new_stream = io.TextIOWrapper(
_stream.buffer,
encoding="utf-8",
errors="replace",
line_buffering=True,
)
setattr(sys, _stream_name, _new_stream)
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Clean up temporary variables
del _stream_name, _stream
if "_new_stream" in dir():
del _new_stream
from cli.utils import find_specs_dir
+31
View File
@@ -13,6 +13,7 @@ Features:
from __future__ import annotations
import asyncio
import io
import json
import logging
import re
@@ -20,6 +21,36 @@ import sys
from pathlib import Path
from typing import TYPE_CHECKING
# Configure safe encoding on Windows to handle Unicode characters in output
# This is needed because this module may be imported in contexts that log
# Unicode content, and the logging system uses stdout/stderr.
if sys.platform == "win32":
for _stream_name in ("stdout", "stderr"):
_stream = getattr(sys, _stream_name)
# Method 1: Try reconfigure (works for TTY)
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
continue
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Method 2: Wrap with TextIOWrapper for piped output
try:
if hasattr(_stream, "buffer"):
_new_stream = io.TextIOWrapper(
_stream.buffer,
encoding="utf-8",
errors="replace",
line_buffering=True,
)
setattr(sys, _stream_name, _new_stream)
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Clean up temporary variables
del _stream_name, _stream
if "_new_stream" in dir():
del _new_stream
if TYPE_CHECKING:
pass
+30
View File
@@ -26,9 +26,39 @@ The context builder will:
4. Output focused context for AI agents
"""
import io
import json
import sys
from pathlib import Path
# Configure safe encoding on Windows to handle Unicode characters in output
if sys.platform == "win32":
for _stream_name in ("stdout", "stderr"):
_stream = getattr(sys, _stream_name)
# Method 1: Try reconfigure (works for TTY)
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
continue
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Method 2: Wrap with TextIOWrapper for piped output
try:
if hasattr(_stream, "buffer"):
_new_stream = io.TextIOWrapper(
_stream.buffer,
encoding="utf-8",
errors="replace",
line_buffering=True,
)
setattr(sys, _stream_name, _new_stream)
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Clean up temporary variables
del _stream_name, _stream
if "_new_stream" in dir():
del _new_stream
from context import (
ContextBuilder,
FileMatch,
+31
View File
@@ -48,6 +48,37 @@ Usage Examples:
pass
"""
import io
import sys
# Configure safe encoding on Windows to handle Unicode characters in output
if sys.platform == "win32":
for _stream_name in ("stdout", "stderr"):
_stream = getattr(sys, _stream_name)
# Method 1: Try reconfigure (works for TTY)
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
continue
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Method 2: Wrap with TextIOWrapper for piped output
try:
if hasattr(_stream, "buffer"):
_new_stream = io.TextIOWrapper(
_stream.buffer,
encoding="utf-8",
errors="replace",
line_buffering=True,
)
setattr(sys, _stream_name, _new_stream)
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Clean up temporary variables
del _stream_name, _stream
if "_new_stream" in dir():
del _new_stream
# Re-export all public functions from the memory package
from memory import (
append_gotcha,
+29
View File
@@ -12,9 +12,38 @@ Usage:
"""
import argparse
import io
import sys
from pathlib import Path
# Configure safe encoding on Windows to handle Unicode characters in output
if sys.platform == "win32":
for _stream_name in ("stdout", "stderr"):
_stream = getattr(sys, _stream_name)
# Method 1: Try reconfigure (works for TTY)
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
continue
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Method 2: Wrap with TextIOWrapper for piped output
try:
if hasattr(_stream, "buffer"):
_new_stream = io.TextIOWrapper(
_stream.buffer,
encoding="utf-8",
errors="replace",
line_buffering=True,
)
setattr(sys, _stream_name, _new_stream)
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Clean up temporary variables
del _stream_name, _stream
if "_new_stream" in dir():
del _new_stream
from .file_timeline import FileTimelineTracker
+29
View File
@@ -15,6 +15,7 @@ Output:
"""
import argparse
import io
import json
import re
import sys
@@ -22,6 +23,34 @@ import urllib.error
import urllib.request
from typing import Any
# Configure safe encoding on Windows to handle Unicode characters in output
if sys.platform == "win32":
for _stream_name in ("stdout", "stderr"):
_stream = getattr(sys, _stream_name)
# Method 1: Try reconfigure (works for TTY)
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
continue
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Method 2: Wrap with TextIOWrapper for piped output
try:
if hasattr(_stream, "buffer"):
_new_stream = io.TextIOWrapper(
_stream.buffer,
encoding="utf-8",
errors="replace",
line_buffering=True,
)
setattr(sys, _stream_name, _new_stream)
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Clean up temporary variables
del _stream_name, _stream
if "_new_stream" in dir():
del _new_stream
DEFAULT_OLLAMA_URL = "http://localhost:11434"
# Minimum Ollama version required for newer embedding models (qwen3-embedding, etc.)
+30
View File
@@ -18,9 +18,39 @@ Usage:
python auto-claude/planner.py --spec-dir auto-claude/specs/001-feature/
"""
import io
import json
import sys
from pathlib import Path
# Configure safe encoding on Windows to handle Unicode characters in output
if sys.platform == "win32":
for _stream_name in ("stdout", "stderr"):
_stream = getattr(sys, _stream_name)
# Method 1: Try reconfigure (works for TTY)
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
continue
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Method 2: Wrap with TextIOWrapper for piped output
try:
if hasattr(_stream, "buffer"):
_new_stream = io.TextIOWrapper(
_stream.buffer,
encoding="utf-8",
errors="replace",
line_buffering=True,
)
setattr(sys, _stream_name, _new_stream)
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Clean up temporary variables
del _stream_name, _stream
if "_new_stream" in dir():
del _new_stream
from implementation_plan import ImplementationPlan
from planner_lib.context import ContextLoader
from planner_lib.generators import get_plan_generator
+29
View File
@@ -10,10 +10,39 @@ Usage:
python prediction.py auto-claude/specs/001-feature/
"""
import io
import json
import sys
from pathlib import Path
# Configure safe encoding on Windows to handle Unicode characters in output
if sys.platform == "win32":
for _stream_name in ("stdout", "stderr"):
_stream = getattr(sys, _stream_name)
# Method 1: Try reconfigure (works for TTY)
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
continue
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Method 2: Wrap with TextIOWrapper for piped output
try:
if hasattr(_stream, "buffer"):
_new_stream = io.TextIOWrapper(
_stream.buffer,
encoding="utf-8",
errors="replace",
line_buffering=True,
)
setattr(sys, _stream_name, _new_stream)
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Clean up temporary variables
del _stream_name, _stream
if "_new_stream" in dir():
del _new_stream
from prediction import generate_subtask_checklist
+29
View File
@@ -18,6 +18,7 @@ Output:
import argparse
import asyncio
import io
import json
import os
import re
@@ -25,6 +26,34 @@ import sys
from datetime import datetime
from pathlib import Path
# Configure safe encoding on Windows to handle Unicode characters in output
if sys.platform == "win32":
for _stream_name in ("stdout", "stderr"):
_stream = getattr(sys, _stream_name)
# Method 1: Try reconfigure (works for TTY)
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
continue
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Method 2: Wrap with TextIOWrapper for piped output
try:
if hasattr(_stream, "buffer"):
_new_stream = io.TextIOWrapper(
_stream.buffer,
encoding="utf-8",
errors="replace",
line_buffering=True,
)
setattr(sys, _stream_name, _new_stream)
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Clean up temporary variables
del _stream_name, _stream
if "_new_stream" in dir():
del _new_stream
# Apply LadybugDB monkeypatch BEFORE any graphiti imports
def apply_monkeypatch():
+29
View File
@@ -36,9 +36,38 @@ Usage:
python auto-claude/review.py --spec-dir auto-claude/specs/001-feature
"""
import io
import sys
from pathlib import Path
# Configure safe encoding on Windows to handle Unicode characters in output
if sys.platform == "win32":
for _stream_name in ("stdout", "stderr"):
_stream = getattr(sys, _stream_name)
# Method 1: Try reconfigure (works for TTY)
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
continue
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Method 2: Wrap with TextIOWrapper for piped output
try:
if hasattr(_stream, "buffer"):
_new_stream = io.TextIOWrapper(
_stream.buffer,
encoding="utf-8",
errors="replace",
line_buffering=True,
)
setattr(sys, _stream_name, _new_stream)
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Clean up temporary variables
del _stream_name, _stream
if "_new_stream" in dir():
del _new_stream
# Re-export all public APIs from the review package
from review import (
ReviewState,
@@ -2,11 +2,41 @@
Main orchestrator for AI-powered project analysis.
"""
import io
import sys
import time
from datetime import datetime
from pathlib import Path
from typing import Any
# Configure safe encoding on Windows to handle Unicode characters in output
if sys.platform == "win32":
for _stream_name in ("stdout", "stderr"):
_stream = getattr(sys, _stream_name)
# Method 1: Try reconfigure (works for TTY)
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
continue
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Method 2: Wrap with TextIOWrapper for piped output
try:
if hasattr(_stream, "buffer"):
_new_stream = io.TextIOWrapper(
_stream.buffer,
encoding="utf-8",
errors="replace",
line_buffering=True,
)
setattr(sys, _stream_name, _new_stream)
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Clean up temporary variables
del _stream_name, _stream
if "_new_stream" in dir():
del _new_stream
from .analyzers import AnalyzerFactory
from .cache_manager import CacheManager
from .claude_client import CLAUDE_SDK_AVAILABLE, ClaudeAnalysisClient
@@ -17,9 +17,39 @@ Example:
"""
import asyncio
import io
import json
import sys
from pathlib import Path
# Configure safe encoding on Windows to handle Unicode characters in output
if sys.platform == "win32":
for _stream_name in ("stdout", "stderr"):
_stream = getattr(sys, _stream_name)
# Method 1: Try reconfigure (works for TTY)
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
continue
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Method 2: Wrap with TextIOWrapper for piped output
try:
if hasattr(_stream, "buffer"):
_new_stream = io.TextIOWrapper(
_stream.buffer,
encoding="utf-8",
errors="replace",
line_buffering=True,
)
setattr(sys, _stream_name, _new_stream)
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Clean up temporary variables
del _stream_name, _stream
if "_new_stream" in dir():
del _new_stream
def main() -> int:
"""CLI entry point."""
+29
View File
@@ -20,9 +20,38 @@ Usage:
"""
import asyncio
import io
import sys
from pathlib import Path
# Configure safe encoding on Windows to handle Unicode characters in output
if sys.platform == "win32":
for _stream_name in ("stdout", "stderr"):
_stream = getattr(sys, _stream_name)
# Method 1: Try reconfigure (works for TTY)
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
continue
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Method 2: Wrap with TextIOWrapper for piped output
try:
if hasattr(_stream, "buffer"):
_new_stream = io.TextIOWrapper(
_stream.buffer,
encoding="utf-8",
errors="replace",
line_buffering=True,
)
setattr(sys, _stream_name, _new_stream)
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Clean up temporary variables
del _stream_name, _stream
if "_new_stream" in dir():
del _new_stream
# Add auto-claude to path
sys.path.insert(0, str(Path(__file__).parent.parent))
+29
View File
@@ -8,10 +8,39 @@ about a codebase. It can also suggest tasks based on the conversation.
import argparse
import asyncio
import io
import json
import sys
from pathlib import Path
# Configure safe encoding on Windows to handle Unicode characters in output
if sys.platform == "win32":
for _stream_name in ("stdout", "stderr"):
_stream = getattr(sys, _stream_name)
# Method 1: Try reconfigure (works for TTY)
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
continue
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Method 2: Wrap with TextIOWrapper for piped output
try:
if hasattr(_stream, "buffer"):
_new_stream = io.TextIOWrapper(
_stream.buffer,
encoding="utf-8",
errors="replace",
line_buffering=True,
)
setattr(sys, _stream_name, _new_stream)
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Clean up temporary variables
del _stream_name, _stream
if "_new_stream" in dir():
del _new_stream
# Add auto-claude to path
sys.path.insert(0, str(Path(__file__).parent.parent))
+29
View File
@@ -15,9 +15,38 @@ Usage:
"""
import asyncio
import io
import sys
from pathlib import Path
# Configure safe encoding on Windows to handle Unicode characters in output
if sys.platform == "win32":
for _stream_name in ("stdout", "stderr"):
_stream = getattr(sys, _stream_name)
# Method 1: Try reconfigure (works for TTY)
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
continue
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Method 2: Wrap with TextIOWrapper for piped output
try:
if hasattr(_stream, "buffer"):
_new_stream = io.TextIOWrapper(
_stream.buffer,
encoding="utf-8",
errors="replace",
line_buffering=True,
)
setattr(sys, _stream_name, _new_stream)
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Clean up temporary variables
del _stream_name, _stream
if "_new_stream" in dir():
del _new_stream
# Add auto-claude to path
sys.path.insert(0, str(Path(__file__).parent.parent))
+31
View File
@@ -23,6 +23,37 @@ The security system has three layers:
See project_analyzer.py for the detection logic.
"""
import io
import sys
# Configure safe encoding on Windows to handle Unicode characters in output
if sys.platform == "win32":
for _stream_name in ("stdout", "stderr"):
_stream = getattr(sys, _stream_name)
# Method 1: Try reconfigure (works for TTY)
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
continue
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Method 2: Wrap with TextIOWrapper for piped output
try:
if hasattr(_stream, "buffer"):
_new_stream = io.TextIOWrapper(
_stream.buffer,
encoding="utf-8",
errors="replace",
line_buffering=True,
)
setattr(sys, _stream_name, _new_stream)
except (AttributeError, io.UnsupportedOperation, OSError):
pass
# Clean up temporary variables
del _stream_name, _stream
if "_new_stream" in dir():
del _new_stream
# Import everything from the security module to maintain backward compatibility
from security import * # noqa: F401, F403
+11
View File
@@ -47,8 +47,19 @@ def pytest_configure(config):
This hook ensures that the real ui module is imported before test collection starts.
This prevents test_spec_pipeline's mocking from affecting the ui module during
collection of ui tests.
Also configures UTF-8 encoding for stdout/stderr to handle Unicode on Windows.
"""
import importlib
import io
# Configure UTF-8 encoding for stdout/stderr on all platforms
# This is critical for Windows tests that use Unicode characters
# Without this, pytest's output capture fails with UnicodeEncodeError on Windows
if sys.stdout.encoding.lower() not in ('utf-8', 'utf_8', 'utf8'):
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
if sys.stderr.encoding.lower() not in ('utf-8', 'utf_8', 'utf8'):
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
# Import the real ui module and its submodules BEFORE test collection
# This ensures they exist in sys.modules before any test can mock them
+18
View File
@@ -109,6 +109,8 @@ class TestUnstageGitignoredFiles:
cwd=git_project,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
assert "test.log" in result_before.stdout
@@ -123,6 +125,8 @@ class TestUnstageGitignoredFiles:
cwd=git_project,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
# We expect unstaging if git check-ignore reports it
# (depends on .gitignore configuration)
@@ -152,6 +156,8 @@ class TestUnstageGitignoredFiles:
cwd=git_project,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
assert ".auto-claude" not in result.stdout
@@ -180,6 +186,8 @@ class TestUnstageGitignoredFiles:
cwd=git_project,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
assert ".auto-claude" not in result.stdout
@@ -222,6 +230,8 @@ class TestUnstageGitignoredFiles:
cwd=git_project,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
assert "normal.txt" in result_before.stdout
@@ -235,6 +245,8 @@ class TestUnstageGitignoredFiles:
cwd=git_project,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
assert "normal.txt" in result.stdout # Normal file should remain staged
assert ".auto-claude" not in result.stdout # .auto-claude should always be unstaged
@@ -266,6 +278,8 @@ class TestCreateWorktree:
cwd=git_project,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
assert str(result.path) in worktree_list.stdout
@@ -611,6 +625,8 @@ class TestMergeWorktree:
cwd=git_project,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
assert "normal.txt" in result.stdout # Normal file should be staged
# test.log may or may not be staged depending on git check-ignore behavior
@@ -642,6 +658,8 @@ class TestMergeWorktree:
cwd=git_project,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
assert "normal.txt" in result.stdout
assert ".auto-claude" not in result.stdout
+2
View File
@@ -618,6 +618,8 @@ class TestCLIActualExecution:
],
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
cwd=str(backend_path),
)
+2
View File
@@ -722,6 +722,8 @@ class TestMainModuleCliExecution:
[sys.executable, str(main_path)],
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=5,
)
# Should show usage message
+4
View File
@@ -27,6 +27,8 @@ def _run_validate_spec(spec_dir: str, *args: str) -> subprocess.CompletedProcess
cmd,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
cwd=str(SCRIPT_PATH.parent),
env=env,
)
@@ -43,6 +45,8 @@ class TestMainCli:
[sys.executable, str(SCRIPT_PATH)],
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
cwd=str(SCRIPT_PATH.parent),
env=env,
)
+3 -1
View File
@@ -83,7 +83,9 @@ def get_latest_commit(project_dir: Path) -> str:
["git", "rev-parse", "HEAD"],
cwd=project_dir,
capture_output=True,
text=True
text=True,
encoding="utf-8",
errors="replace",
)
return result.stdout.strip() if result.returncode == 0 else ""
+2
View File
@@ -68,6 +68,8 @@ def _check_glab_detection() -> bool:
[glab_path, "--version"],
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=5,
)
if result.returncode == 0:
+4
View File
@@ -123,6 +123,8 @@ def temp_git_repo():
check=True,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
commit_sha = result.stdout.strip()
@@ -134,6 +136,8 @@ def temp_git_repo():
check=True,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
assert status_result.stdout.strip() == "", f"Git repo not clean: {status_result.stdout}"
+6 -2
View File
@@ -250,7 +250,9 @@ def test_good_commit_tracking():
["git", "rev-parse", "HEAD"],
cwd=project_dir,
capture_output=True,
text=True
text=True,
encoding="utf-8",
errors="replace"
)
commit_hash = result.stdout.strip()
@@ -272,7 +274,9 @@ def test_good_commit_tracking():
["git", "rev-parse", "HEAD"],
cwd=project_dir,
capture_output=True,
text=True
text=True,
encoding="utf-8",
errors="replace"
)
commit_hash2 = result.stdout.strip()
+4
View File
@@ -27,6 +27,8 @@ class TestSubprocessTimeout:
["echo", "hello"],
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=5,
)
@@ -578,6 +580,8 @@ class TestTimeoutErrorHandling:
["sleep", "10"],
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=0.1,
)
output = result.stdout
+12
View File
@@ -702,6 +702,8 @@ class TestRebaseSpecBranch:
cwd=temp_git_repo,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
).stdout.strip()
# Rebase the spec branch
@@ -715,6 +717,8 @@ class TestRebaseSpecBranch:
cwd=temp_git_repo,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
).stdout.strip()
# Commits should be different (rebase changed the commit hash)
@@ -726,6 +730,8 @@ class TestRebaseSpecBranch:
cwd=temp_git_repo,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
).stdout
assert "Main commit" in log, "Spec branch should have main commit after rebase"
@@ -797,6 +803,8 @@ class TestRebaseSpecBranch:
cwd=temp_git_repo,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
assert current_branch.stdout.strip() == "main", "Should still be on main branch"
@@ -812,6 +820,8 @@ class TestRebaseSpecBranch:
cwd=temp_git_repo,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
assert status_result.stdout.strip() == "", "Git status should be clean"
@@ -983,6 +993,8 @@ class TestRebaseErrorHandling:
cwd=temp_git_repo,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
commit_hash = commit_result.stdout.strip()
subprocess.run(
+4 -4
View File
@@ -142,7 +142,7 @@ class TestWorktreeRemoval:
# Verify branch is deleted
result = subprocess.run(
["git", "branch", "--list", branch_name],
cwd=temp_git_repo, capture_output=True, text=True
cwd=temp_git_repo, capture_output=True, text=True, encoding="utf-8", errors="replace"
)
assert branch_name not in result.stdout
@@ -287,7 +287,7 @@ class TestWorktreeCommitAndMerge:
# Verify branch was deleted
branch_list_result = subprocess.run(
["git", "branch", "--list", branch_name],
cwd=temp_git_repo, capture_output=True, text=True
cwd=temp_git_repo, capture_output=True, text=True, encoding="utf-8", errors="replace"
)
assert branch_name not in branch_list_result.stdout, f"Branch {branch_name} should be deleted"
@@ -342,7 +342,7 @@ class TestWorktreeCommitAndMerge:
# Verify git status shows no unmerged/conflict status codes
git_status = subprocess.run(
["git", "status", "--porcelain"],
cwd=temp_git_repo, capture_output=True, text=True
cwd=temp_git_repo, capture_output=True, text=True, encoding="utf-8", errors="replace"
)
# Should have no output (clean working directory)
assert git_status.returncode == 0
@@ -399,7 +399,7 @@ class TestWorktreeCommitAndMerge:
# Verify git status shows no staged/unstaged changes
git_status = subprocess.run(
["git", "status", "--porcelain"],
cwd=temp_git_repo, capture_output=True, text=True
cwd=temp_git_repo, capture_output=True, text=True, encoding="utf-8", errors="replace"
)
assert git_status.returncode == 0
assert not git_status.stdout.strip(), f"Expected clean status, got: {git_status.stdout}"