Skip to content

Commit 810d113

Browse files
committed
fix(agent-context): require yaml-capable updater python
1 parent 6d17da6 commit 810d113

3 files changed

Lines changed: 107 additions & 17 deletions

File tree

extensions/agent-context/scripts/bash/update-agent-context.sh

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,31 @@ if [[ ! -f "$EXT_CONFIG" ]]; then
2626
exit 0
2727
fi
2828

29-
# Locate a suitable Python interpreter (SPECKIT_PYTHON, then python3, then python).
29+
# Locate a Python 3 interpreter with PyYAML available.
3030
_python=""
31-
if [[ -n "${SPECKIT_PYTHON:-}" ]]; then
32-
_python="$SPECKIT_PYTHON"
33-
elif command -v python3 >/dev/null 2>&1; then
34-
_python="python3"
35-
elif command -v python >/dev/null 2>&1 && python --version 2>&1 | grep -q "^Python 3"; then
36-
_python="python"
37-
fi
31+
_python_candidates=()
32+
[[ -n "${SPECKIT_PYTHON:-}" ]] && _python_candidates+=("$SPECKIT_PYTHON")
33+
_python_candidates+=("python3" "python")
34+
for _candidate in "${_python_candidates[@]}"; do
35+
if command -v "$_candidate" >/dev/null 2>&1 \
36+
&& "$_candidate" - <<'PY' >/dev/null 2>&1
37+
import sys
38+
try:
39+
import yaml # noqa: F401
40+
except ImportError:
41+
sys.exit(1)
42+
sys.exit(0 if sys.version_info[0] == 3 else 1)
43+
PY
44+
then
45+
_python="$_candidate"
46+
break
47+
fi
48+
done
49+
unset _candidate _python_candidates
3850

39-
if [[ -z "$_python" ]] || ! "$_python" --version 2>&1 | grep -q "^Python 3"; then
40-
echo "agent-context: Python 3 not found on PATH; skipping update." >&2
51+
if [[ -z "$_python" ]]; then
52+
echo "agent-context: Python 3 with PyYAML not found on PATH; skipping update." >&2
53+
echo " To resolve: pip install pyyaml (or install it into the environment used by python3)." >&2
4154
exit 0
4255
fi
4356
_case_insensitive_context_files=0

extensions/agent-context/scripts/powershell/update-agent-context.ps1

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,9 @@ if ($null -eq $Options) {
142142
$pythonCandidates += @('python3', 'python')
143143
foreach ($candidate in $pythonCandidates) {
144144
if (Get-Command $candidate -ErrorAction SilentlyContinue) {
145-
# Verify it is Python 3
146-
$verOut = & $candidate --version 2>&1
147-
if ($verOut -match 'Python 3') {
145+
# Verify it is Python 3 with PyYAML available.
146+
$null = & $candidate -c "import sys; import yaml; sys.exit(0 if sys.version_info[0] == 3 else 1)" 2>$null
147+
if ($LASTEXITCODE -eq 0) {
148148
$pythonCmd = $candidate
149149
break
150150
}

tests/extensions/test_extension_agent_context.py

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,19 +180,34 @@ def _ensure_test_python_on_path(project_root: Path) -> Path:
180180
return shim_dir
181181

182182

183-
def _bundled_script_env(project_root: Path, *, for_bash: bool = False) -> dict[str, str]:
183+
def _bundled_script_env(
184+
project_root: Path,
185+
*,
186+
for_bash: bool = False,
187+
speckit_python: str | None = None,
188+
) -> dict[str, str]:
184189
env = os.environ.copy()
185190
shim_dir = _ensure_test_python_on_path(project_root)
186191
env["PATH"] = str(shim_dir) + os.pathsep + env.get("PATH", "")
187192
env["SPECKIT_PYTHON"] = (
188-
_bash_posix_path(Path(sys.executable)) if for_bash else sys.executable
193+
speckit_python
194+
if speckit_python is not None
195+
else (_bash_posix_path(Path(sys.executable)) if for_bash else sys.executable)
189196
)
190197
return env
191198

192199

193-
def _run_bash_agent_context_script(project_root: Path) -> subprocess.CompletedProcess:
200+
def _run_bash_agent_context_script(
201+
project_root: Path,
202+
*,
203+
speckit_python: str | None = None,
204+
) -> subprocess.CompletedProcess:
194205
script = EXT_DIR / "scripts" / "bash" / "update-agent-context.sh"
195-
env = _bundled_script_env(project_root, for_bash=True)
206+
env = _bundled_script_env(
207+
project_root,
208+
for_bash=True,
209+
speckit_python=speckit_python,
210+
)
196211
if os.name == "nt":
197212
root = _bash_posix_path(project_root)
198213
script_path = _bash_posix_path(script)
@@ -242,6 +257,30 @@ def _run_powershell_agent_context_script(project_root: Path) -> subprocess.Compl
242257
)
243258

244259

260+
def _run_powershell_agent_context_script_with_env(
261+
project_root: Path,
262+
*,
263+
speckit_python: str,
264+
) -> subprocess.CompletedProcess:
265+
script = EXT_DIR / "scripts" / "powershell" / "update-agent-context.ps1"
266+
env = _bundled_script_env(project_root, speckit_python=speckit_python)
267+
return subprocess.run(
268+
[
269+
POWERSHELL,
270+
"-NoProfile",
271+
"-ExecutionPolicy",
272+
"Bypass",
273+
"-File",
274+
str(script),
275+
],
276+
cwd=project_root,
277+
env=env,
278+
capture_output=True,
279+
text=True,
280+
timeout=30,
281+
)
282+
283+
245284
class TestContextMarkerResolution:
246285
def test_defaults_when_ext_config_missing(self, tmp_path):
247286
i = _CtxIntegration()
@@ -737,6 +776,25 @@ def test_bash_script_deduplicates_context_files_in_order(self, tmp_path):
737776
assert output.count("agent-context: updated CLAUDE.md") == 1
738777
assert "agent-context: updated agents.md" not in output
739778

779+
@requires_bash
780+
def test_bash_script_falls_back_from_invalid_speckit_python(self, tmp_path):
781+
project = tmp_path / "project"
782+
project.mkdir()
783+
_install_agent_context_config(
784+
project,
785+
context_file="AGENTS.md",
786+
context_files=["AGENTS.md"],
787+
)
788+
789+
result = _run_bash_agent_context_script(
790+
project,
791+
speckit_python="/definitely/missing/python",
792+
)
793+
794+
assert result.returncode == 0, result.stderr + result.stdout
795+
assert "agent-context: updated AGENTS.md" in (result.stderr + result.stdout)
796+
assert (project / "AGENTS.md").exists()
797+
740798
@pytest.mark.skipif(POWERSHELL is None, reason="PowerShell not available")
741799
def test_powershell_script_rejects_backslash_context_files(self, tmp_path):
742800
project = tmp_path / "project"
@@ -774,6 +832,25 @@ def test_powershell_script_deduplicates_context_files_in_order(self, tmp_path):
774832
assert output.count("agent-context: updated CLAUDE.md") == 1
775833
assert "agent-context: updated agents.md" not in output
776834

835+
@pytest.mark.skipif(POWERSHELL is None, reason="PowerShell not available")
836+
def test_powershell_script_falls_back_from_invalid_speckit_python(self, tmp_path):
837+
project = tmp_path / "project"
838+
project.mkdir()
839+
_install_agent_context_config(
840+
project,
841+
context_file="AGENTS.md",
842+
context_files=["AGENTS.md"],
843+
)
844+
845+
result = _run_powershell_agent_context_script_with_env(
846+
project,
847+
speckit_python=str(project / "missing-python"),
848+
)
849+
850+
assert result.returncode == 0, result.stderr + result.stdout
851+
assert "agent-context: updated AGENTS.md" in (result.stderr + result.stdout)
852+
assert (project / "AGENTS.md").exists()
853+
777854
@pytest.mark.skipif(
778855
POWERSHELL is None or os.name != "nt",
779856
reason="Windows PowerShell junction test requires Windows",

0 commit comments

Comments
 (0)