-
Notifications
You must be signed in to change notification settings - Fork 1
Implementation Plan: T5-P5-A10-WP1 — CLI Config-Acceptance Probe (Check 34) #4140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2b5215a
40a80c0
2e40faf
c87ac30
fb5397e
9aef3da
1f03afc
b7aebec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| # doctor/ | ||
|
|
||
| Diagnostic health checks for the autoskillit installation (33 checks). | ||
| Diagnostic health checks for the autoskillit installation (34 checks). | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [warning] cohesion: AGENTS.md says 34 checks but docs/installation.md prose says 33. These two authoritative sources give different totals — whichever is wrong must be corrected. |
||
|
|
||
| ## Files | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,14 +3,17 @@ | |
| from __future__ import annotations | ||
|
|
||
| import json | ||
| import shlex | ||
| import subprocess | ||
| import tempfile | ||
| from pathlib import Path | ||
|
|
||
| import regex as re | ||
|
|
||
| from autoskillit.core import ( | ||
| CodingAgentBackend, | ||
| Severity, | ||
| atomic_write, | ||
| default_log_dir, | ||
| get_logger, | ||
| ) | ||
|
|
@@ -270,3 +273,47 @@ def _check_codex_graduation( | |
| summary += f" — EXPERIMENTAL hold: {pending} of 4 criteria pending" | ||
|
|
||
| return DoctorResult(Severity.INFO, check_name, summary) | ||
|
|
||
|
|
||
| def _check_cli_conformance_probes(*, backend: CodingAgentBackend | None = None) -> DoctorResult: | ||
| check_name = "cli_conformance_probes" | ||
|
|
||
| if backend is None or not backend.capabilities.version_check_command: | ||
| return DoctorResult(Severity.OK, check_name, "Skipped (no version check command)") | ||
|
|
||
| cli_argv = shlex.split(backend.capabilities.version_check_command) | ||
| try: | ||
| subprocess.run(cli_argv, capture_output=True, text=True, timeout=5) | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [warning] defense: |
||
| except (FileNotFoundError, subprocess.TimeoutExpired, OSError): | ||
| return DoctorResult(Severity.OK, check_name, "CLI unavailable; skipping config probe") | ||
|
|
||
| cli_binary = cli_argv[0] | ||
| result = None | ||
| with tempfile.TemporaryDirectory() as tmpdir: | ||
| probe_path = Path(tmpdir) / "probe.toml" | ||
| atomic_write(probe_path, 'model = "test-model"\n') | ||
| try: | ||
| result = subprocess.run( | ||
| [cli_binary, "-c", str(probe_path), "--version"], | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=10, | ||
| ) | ||
| except subprocess.TimeoutExpired: | ||
| return DoctorResult( | ||
| Severity.OK, | ||
| check_name, | ||
| f"{cli_binary} config probe timed out; skipping", | ||
| ) | ||
| except (FileNotFoundError, OSError): | ||
| return DoctorResult(Severity.OK, check_name, "CLI unavailable; skipping config probe") | ||
|
|
||
| if result is not None and result.returncode == 0: | ||
| return DoctorResult(Severity.OK, check_name, f"{cli_binary} accepted minimal config probe") | ||
| if result is None: | ||
| return DoctorResult(Severity.OK, check_name, "CLI unavailable; skipping config probe") | ||
| return DoctorResult( | ||
| Severity.WARNING, | ||
| check_name, | ||
| f"{cli_binary} rejected config probe (exit {result.returncode})", | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -691,3 +691,117 @@ def test_absent_files_yield_not_yet_run( | |
| assert "probe=not-yet-run" in result.message | ||
| assert "matrix=not-yet-run" in result.message | ||
| assert "smoke=not-found" in result.message | ||
|
|
||
|
|
||
| class TestCheckCliConformanceProbes: | ||
| """Tests for _check_cli_conformance_probes doctor check (Check 34).""" | ||
|
|
||
| def test_skip_for_none_backend(self) -> None: | ||
| from autoskillit.cli.doctor._doctor_runtime import _check_cli_conformance_probes | ||
| from autoskillit.core import Severity | ||
|
|
||
| result = _check_cli_conformance_probes(backend=None) | ||
| assert result.severity == Severity.OK | ||
| assert result.check == "cli_conformance_probes" | ||
| assert "skipped" in result.message.lower() | ||
|
|
||
| def test_skip_for_backend_without_version_check_command(self) -> None: | ||
| from types import SimpleNamespace | ||
|
|
||
| from autoskillit.cli.doctor._doctor_runtime import _check_cli_conformance_probes | ||
| from autoskillit.core import Severity | ||
|
|
||
| stub = SimpleNamespace(capabilities=SimpleNamespace(version_check_command="")) | ||
| result = _check_cli_conformance_probes(backend=stub) | ||
| assert result.severity == Severity.OK | ||
| assert "skipped" in result.message.lower() | ||
|
|
||
| def test_skip_when_cli_unavailable(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||
| from autoskillit.cli.doctor._doctor_runtime import _check_cli_conformance_probes | ||
| from autoskillit.core import Severity | ||
| from autoskillit.execution.backends.codex import CodexBackend | ||
|
|
||
| def _raise(*a, **kw): | ||
| raise FileNotFoundError("codex") | ||
|
|
||
| monkeypatch.setattr("autoskillit.cli.doctor._doctor_runtime.subprocess.run", _raise) | ||
| result = _check_cli_conformance_probes(backend=CodexBackend()) | ||
| assert result.severity == Severity.OK | ||
| assert "unavailable" in result.message.lower() | ||
|
|
||
| def test_skip_when_cli_times_out(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||
| import subprocess | ||
|
|
||
| from autoskillit.cli.doctor._doctor_runtime import _check_cli_conformance_probes | ||
| from autoskillit.core import Severity | ||
| from autoskillit.execution.backends.codex import CodexBackend | ||
|
|
||
| call_count = 0 | ||
|
|
||
| def _stateful_fake(*a, **kw): | ||
| nonlocal call_count | ||
| call_count += 1 | ||
| if call_count == 1: | ||
| return type( | ||
| "CompletedProcess", (), {"returncode": 0, "stdout": "", "stderr": ""} | ||
| )() | ||
| raise subprocess.TimeoutExpired(cmd="codex", timeout=10) | ||
|
|
||
| monkeypatch.setattr( | ||
| "autoskillit.cli.doctor._doctor_runtime.subprocess.run", _stateful_fake | ||
| ) | ||
| result = _check_cli_conformance_probes(backend=CodexBackend()) | ||
| assert result.severity == Severity.OK | ||
| assert "timed out" in result.message.lower() | ||
|
|
||
| def test_ok_when_config_accepted(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||
| import subprocess | ||
|
|
||
| from autoskillit.cli.doctor._doctor_runtime import _check_cli_conformance_probes | ||
| from autoskillit.core import Severity | ||
| from autoskillit.execution.backends.codex import CodexBackend | ||
|
|
||
| calls = [] | ||
|
|
||
| def _fake_run(*a, **kw): | ||
| calls.append(a) | ||
| return type( | ||
| "CompletedProcess", | ||
| (), | ||
| {"returncode": 0, "stdout": "codex 0.130.0\n", "stderr": ""}, | ||
| )() | ||
|
|
||
| monkeypatch.setattr(subprocess, "run", _fake_run) | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [warning] tests: |
||
| result = _check_cli_conformance_probes(backend=CodexBackend()) | ||
| assert result.severity == Severity.OK | ||
| assert "accepted" in result.message.lower() | ||
| assert len(calls) == 2 | ||
|
|
||
| def test_warning_when_config_rejected(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||
| import subprocess | ||
|
|
||
| from autoskillit.cli.doctor._doctor_runtime import _check_cli_conformance_probes | ||
| from autoskillit.core import Severity | ||
| from autoskillit.execution.backends.codex import CodexBackend | ||
|
|
||
| call_count = 0 | ||
|
|
||
| def _fake_run(*a, **kw): | ||
| nonlocal call_count | ||
| call_count += 1 | ||
| if call_count == 1: | ||
| return type( | ||
| "CompletedProcess", | ||
| (), | ||
| {"returncode": 0, "stdout": "codex 0.130.0\n", "stderr": ""}, | ||
| )() | ||
| return type( | ||
| "CompletedProcess", | ||
| (), | ||
| {"returncode": 1, "stdout": "", "stderr": "unknown flag"}, | ||
| )() | ||
|
|
||
| monkeypatch.setattr(subprocess, "run", _fake_run) | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [warning] tests: Same monkeypatch scope bug as L774 — |
||
| result = _check_cli_conformance_probes(backend=CodexBackend()) | ||
| assert result.severity == Severity.WARNING | ||
| assert "rejected" in result.message.lower() or "exit 1" in result.message | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [warning] tests: Weak disjunctive assertion — |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[warning] cohesion: prose says "Doctor runs 33 checks" but src/autoskillit/cli/doctor/AGENTS.md says "34 checks" — one count must be wrong. Verify whether the base (non-fleet) total is 33 (23 numbered + 5 lettered + 5 backend runtime probes 30–34) or 34, and update both files to agree.