Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions MANIFEST.sha256
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,13 @@ a2a2977403154705fc3d73fde0f45548c4722cc20b11cc189fe44f7982899224 ./docs/plans/c
a5d8013da3d22afba28c1259e29e3255d1d19969d0934b2f59069f0de80ea31b ./docs/plans/cpk-057-review-and-astra-guidance.md
5f2abf3bc85fbfc580d05139165ff95f29a3a85246bab2724c5f949eb93f26a5 ./docs/plans/cpk-058-plan-history-compaction.md
43141b61bf800dc9956603b6e88cdd0c3a5564340d3f4aee3fb6b0682dceee00 ./docs/plans/cpk-059-public-pr-release.md
c14438f22cad4a83b4409a72e75b589d19440d20516511bd7d4a419b27deb7ed ./docs/roadmap.md
8cea5185f3a1181dc9a1419f3f980712b2b9e35936ec2a8011629d67cd0d041f ./docs/plans/cpk-060-windows-codex-resolution.md
23106fd9f30090fc85692c14612b95f69be5b8d2757137a3df19ed6093d2af0b ./docs/roadmap.md
6f8f14ca5fd8ad935eee66256b3f7c49d5c40957eedff938ba21365e30869719 ./doctor.ps1
ab56a88d9188979f06ea142d1f58ea0d6ad9088e83a965bd324209d83da513ad ./doctor.sh
8087bdb3ee654c0baac278a8e3bf85e33e314f8acfd486e7ebf675126a697058 ./install.ps1
3466d2e34145cfdaa83d793960794b2aefc96f3d9c48d22fd2af930ec819e9c4 ./install.sh
7843fa0b49e088495e603e8eb3ae6811cb40007d7b40bf3e621f7855d1722159 ./kit.py
7c4238f798f567de9b7de28268b5fd344a861ce8fff1d16da7f2e2966908f8f5 ./kit.py
331f17ca1486b5779d9b57a3a73834328eb3089ce612e636e6a8e5085899d61e ./run-tests.ps1
e6df9f1da02280a66161583529b06a34f0103041e9587afad0fac6074eecc15b ./run-tests.sh
85a2ba72e2b0cd1ab6c25a51f39059502415c3ac62a6bffa613d7e31b6422cd4 ./setup-repo.ps1
Expand All @@ -140,8 +141,8 @@ e1e231aac51822f7e1bb3aa61aba138cf8c5df9cefd3079b0c1b5a98eea03bd9 ./tests/fixtur
a269775204ae7cad2457d00f5565c90da2df41a9719479f3489ec01afdd2519c ./tests/fixtures/cpk056-agent-cases.json
e32941eb74be1f2cdcb6c82cf71e113b841651bf0d01786f364176de2da1b9c5 ./tests/fixtures/cpk056-agent-grading.json
289367b7090a0d71936b41f08c4a354bc2758013dceb709819de36a1b73d9aa6 ./tests/live_plan_history.py
74d96d387c814f60e37f0ba682bb42e1ba05b729c866b929d026166a4a9be4c9 ./tests/test_kit.py
cfa479b5248a628ecd71194b7ac4a36bcddf17af7eb52ca2a6c3cd0566269927 ./tests/test_ponytail.py
9b6d3048643461026467186632f3de49fbc533aaa8a91de46b699c67cfe7bad3 ./tests/test_kit.py
1979109d44a6b49c01747216c99dc6598e5cc725b986df4eea60215e4598f4d8 ./tests/test_ponytail.py
d16f7eadc8c668e82a1d0d2554ed04def15cb0a4b2ce13ac1ce1339bc0e62161 ./uninstall.ps1
f12cff94ef7f9b0ca489cbe43e1de737011476966e04c1578f4977177b0da217 ./uninstall.sh
e96e2cd99b37377ca84d420d142d097981f303f5006d930880352ad9404d3b13 ./upstream.lock.json
40 changes: 40 additions & 0 deletions docs/plans/cpk-060-windows-codex-resolution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Resolve the Codex command on Windows

This ExecPlan records the scope and validation for CPK-060. Maintain it according to `.agent/PLANS.md` until review closure.

## Purpose

The Windows installer and Doctor must run the npm-installed Codex command. Python cannot start the extensionless `codex` name directly when only the Windows npm command shim is available.

## Scope

Resolve `codex` with the Python standard library on Windows and reuse that path for plugin installation and Doctor checks. Keep the existing command name on macOS and Linux. Isolate Git test fixtures from the host's line-ending conversion so the Windows suite verifies repository bytes consistently.

This bug fix publishes version `0.24.1`. It adds no dependency, configuration option, or new installation path.

## Progress

- [x] Reproduced the Windows command lookup failure.
- [x] Added one shared Windows command resolver and its regression check.
- [x] Isolated Git fixtures from global `core.autocrlf` settings.
- [x] Passed the complete test suite, installer, and Doctor on Windows.
- [x] Completed adversarial review. Publication continues through the protected pull-request workflow.

## Plan history reconciliation

No Plan Mode record applies to CPK-060. The task began from the user's direct bug report and publication request.

## Validation and acceptance

Run from the repository root:

pwsh -File .\run-tests.ps1
pwsh -File .\install.ps1
pwsh -File .\doctor.ps1
git diff --check

Require every manifest checksum to match. Require Doctor to report `Result: ready` for toolkit version `0.24.1` and the installed Codex CLI.

## Outcome

On Windows, the toolkit uses the executable path returned by `shutil.which("codex")`. Native plugin installation and Doctor use the resolved npm `.CMD` shim. Other platforms keep the existing `codex` command. Final adversarial review found no actionable defect.
1 change: 1 addition & 0 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ No residual risk is accepted.

## Completed

- ✅ **CPK-060 — Resolve the Codex command on Windows.** Use the Windows npm command shim for native plugin installation and Doctor checks. Publish version `0.24.1`. [ExecPlan](plans/cpk-060-windows-codex-resolution.md)
- ✅ **CPK-059 — Publish through protected pull requests.** Add readiness-triggered CI, make the repository public, protect main, and release reusable Plan History as version `0.24.0`. [ExecPlan](plans/cpk-059-public-pr-release.md)
- ✅ **CPK-058 — Compact Plan History and resume interrupted reads.** Reuse verified summaries without changing original records, and preserve reading progress across normal context compaction. [ExecPlan](plans/cpk-058-plan-history-compaction.md)
- ✅ **CPK-057 — Separate review from testing and complete Astra guidance.** Keep test execution before review and add bounded autonomy, clear communication, and useful delegation to the existing rule owners. [ExecPlan](plans/cpk-057-review-and-astra-guidance.md)
Expand Down
15 changes: 11 additions & 4 deletions kit.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from typing import Any, Iterable

KIT_ID = "codex-practical-kit"
KIT_VERSION = "0.24.0"
KIT_VERSION = "0.24.1"
REPOWISE_VERSION = "0.45.0"
UV_VERSION = "0.12.4"
UV_INSTALLER_URL = f"https://astral.sh/uv/{UV_VERSION}/install.sh"
Expand Down Expand Up @@ -178,6 +178,12 @@ def command_version(command: list[str]) -> str | None:
return text.splitlines()[0] if text else None


def codex_executable() -> str:
if not is_windows():
return "codex"
return shutil.which("codex") or "codex"


def json_load(path: Path, default: Any) -> Any:
if not path.exists():
return default
Expand Down Expand Up @@ -493,7 +499,7 @@ def manifest_path(paths: InstallPaths) -> Path:

def codex_plugin(paths: InstallPaths, *args: str) -> dict[str, Any]:
result = run(
["codex", "plugin", *args, "--json"],
[codex_executable(), "plugin", *args, "--json"],
env={**os.environ, "CODEX_HOME": str(paths.codex_home)},
)
try:
Expand Down Expand Up @@ -1121,11 +1127,12 @@ def doctor(args: argparse.Namespace, paths: InstallPaths) -> int:
version or "unavailable",
)

codex_version = command_version(["codex", "--version"])
codex = codex_executable()
codex_version = command_version([codex, "--version"])
ok &= check(bool(codex_version), "Codex CLI", codex_version or "not found")
if codex_version:
login = run(
["codex", "login", "status"], check=False, timeout=30,
[codex, "login", "status"], check=False, timeout=30,
env={**os.environ, "CODEX_HOME": str(paths.codex_home)},
)
login_text = (login.stdout or login.stderr).strip().replace("\n", " ")
Expand Down
13 changes: 11 additions & 2 deletions tests/test_kit.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class GitFixture:
def __init__(self, root: Path):
self.root = root
subprocess.run(["git", "init", "-q"], cwd=root, check=True)
subprocess.run(["git", "config", "core.autocrlf", "false"], cwd=root, check=True)
subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=root, check=True)
subprocess.run(["git", "config", "user.name", "Test"], cwd=root, check=True)

Expand Down Expand Up @@ -78,6 +79,14 @@ def test_run_decodes_utf8_subprocess_output(self):
)
self.assertEqual(result.stdout, "✓")

def test_windows_codex_command_resolves_npm_cmd_shim(self):
shim = r"C:\Users\Example\AppData\Roaming\npm\codex.CMD"
with mock.patch.object(kit, "is_windows", return_value=True), mock.patch.object(
kit.shutil, "which", return_value=shim
) as which:
self.assertEqual(kit.codex_executable(), shim)
which.assert_called_once_with("codex")

def paths(self, base: Path) -> kit.InstallPaths:
return kit.InstallPaths(
base / "home", base / "codex", base / "skills", base / "kit"
Expand Down Expand Up @@ -697,7 +706,7 @@ def test_plan_history_stays_internal_for_every_event(self):
subprocess.run(["git", "add", "docs", ".agent/plan-history"], cwd=root, check=True)
subprocess.run(["git", "commit", "-qm", "retain Plan history"], cwd=root, check=True)
clone = Path(temp) / "clone"
subprocess.run(["git", "clone", "-q", str(root), str(clone)], check=True)
subprocess.run(["git", "-c", "core.autocrlf=false", "clone", "-q", str(root), str(clone)], check=True)
cloned, = (clone / ".agent/plan-history").glob("*.md")
self.assertEqual(cloned.read_bytes(), message.encode())
self.assertEqual(list((clone / "docs").glob("*.plan-summary.*.md")), [])
Expand Down Expand Up @@ -3455,7 +3464,7 @@ def test_lifecycle_skills_and_version(self):
self.assertIn("`pwsh -File .\\doctor.ps1`", publication)
self.assertIn("from the reviewed candidate", publication)
self.assertIn("`Result: ready`", publication)
self.assertEqual(kit.KIT_VERSION, "0.24.0")
self.assertEqual(kit.KIT_VERSION, "0.24.1")
self.assertNotIn("Version `0.22.0`", (ROOT / "README.md").read_text())
self.assertNotIn("version 0.22.0", (ROOT / "CODEX-INSTALL-PROMPT.md").read_text())

Expand Down
1 change: 1 addition & 0 deletions tests/test_ponytail.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def setUp(self):
mock.patch.object(kit, "download_file", return_value=b"# upstream fixture\n"),
mock.patch.object(kit, "ensure_repowise_runtime", return_value=("fake-uv", "fake-repowise")),
mock.patch.object(kit, "find_runtime_command", side_effect=lambda paths, name: "fake-" + name),
mock.patch.object(kit, "codex_executable", return_value="codex"),
mock.patch.dict(os.environ, {"CODEX_HOME": str(self.root / "default")}),
):
patch.start()
Expand Down
Loading