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
13 changes: 7 additions & 6 deletions MANIFEST.sha256
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,17 @@ b97c626ce8e220598d12f7bd0cce5099d331aae71a45f692a9eb57d669d0c038 ./check-update
78823831a732ebf870c45b9acc92abddfdddbdbb47db6db81a38ada0497de00f ./docs/OPERATING-MANUAL.md
97dd789229fa55905bd20290c2767ef7bd274cc81965a6f4eb1136dda9ed0adb ./docs/OPTIONAL-REVIEW-TOOLS.md
9bee78762d4d1f446cfb816878e36671cdd823ab62521d32833cc915a572796e ./docs/REMOVE-CBM.md
d69f7436f838d6daa56b7a311cfdbe7f77fbbf1b9c6c2835a813709e75187416 ./docs/REPOWISE.md
6baabd9b2a4c0522d736e6852ddb0668d1fde443c809780fb65c17d5471fed35 ./docs/REPOWISE.md
e27b3d72ff47ebf7230a96b3a5f2d6ea3f410e22ef382f8e7d9cd27c7bb59892 ./docs/REVIEW.md
e86355a4eae8ec5cd42e7674461ebaf1acc4f14f20fa9600da2bb5248efb8e43 ./docs/SPEC-KIT.md
f7298a2735489830b11a83c5a852984916147e8c3408214cc722d58a4f7a139f ./docs/WHY-THIS-SHAPE.md
e03065e7b874d15ba91cb73d2f76978421d1c459a12f19857bb3de02b71f8ed7 ./docs/roadmap.md
7870d79e9ea37e6e5dda6419d1e15537e5f44e439678bf7e07bbe1722d053b36 ./docs/plans/CPK-068-windows-install.md
305bd7b53c9bc86eea7cc16b4b3a462a3d1a87df316c4140a401a102da775610 ./docs/roadmap.md
6f8f14ca5fd8ad935eee66256b3f7c49d5c40957eedff938ba21365e30869719 ./doctor.ps1
ab56a88d9188979f06ea142d1f58ea0d6ad9088e83a965bd324209d83da513ad ./doctor.sh
8087bdb3ee654c0baac278a8e3bf85e33e314f8acfd486e7ebf675126a697058 ./install.ps1
3466d2e34145cfdaa83d793960794b2aefc96f3d9c48d22fd2af930ec819e9c4 ./install.sh
54daa83ee5e415bfb5fac1e30d06309c667f104ed3b59b3aed1a10f0aad51dd2 ./kit.py
2149d19f7de831542b2a9e3a3bd6f5a0a33a09606fa8935a2998f91bc5fd2491 ./kit.py
bbc6440e9686b886bac407dffea3d3a21dc175d784f2645b0b97305214f96023 ./run-tests.ps1
88528842a28f08ec8d96eb014eb93a248d2dede495c6d553151840ec1f9688e2 ./run-tests.sh
85a2ba72e2b0cd1ab6c25a51f39059502415c3ac62a6bffa613d7e31b6422cd4 ./setup-repo.ps1
Expand All @@ -73,9 +74,9 @@ e1e231aac51822f7e1bb3aa61aba138cf8c5df9cefd3079b0c1b5a98eea03bd9 ./tests/fixtur
e65a14cd4d2744b0a9ee0dc9d2d74afa7e1cac835d832419b34be1baacc94379 ./tests/fixtures/cpk056-agent-cases.json
e32941eb74be1f2cdcb6c82cf71e113b841651bf0d01786f364176de2da1b9c5 ./tests/fixtures/cpk056-agent-grading.json
289367b7090a0d71936b41f08c4a354bc2758013dceb709819de36a1b73d9aa6 ./tests/live_plan_history.py
5947183183063da3141431ae2b9247f4033ce2a839d16a559fa8c1abb585d50d ./tests/test_kit.py
dbeb390655fbe5321578256db17b6a7c9edc1a4a7e7499ff5fe524dccbfc1719 ./tests/test_ponytail.py
8fb5aefac7fa8a9cef45296f2dacbcc0663bed19a0daee5d94f71cebce15b012 ./tests/test_updates.py
9bb6bc13f0454f83049d99cc228dbbd2b9f290dea0cb0dece249f74a9e60bf2d ./tests/test_kit.py
f8d4f2bc09562660e441af9c2d20c6a73e7ad92bb105b12f0c6174af95c8c781 ./tests/test_ponytail.py
00127b8f5bd1074949a00181359b5f536995e47de0dc752cf5f5546f3e228172 ./tests/test_updates.py
54673ace20ec6333c73ead405c82d52560848bc8b9aa3a0102fc39935cc3ab87 ./tests/update_subprocess_fixture.py
d16f7eadc8c668e82a1d0d2554ed04def15cb0a4b2ce13ac1ce1339bc0e62161 ./uninstall.ps1
f12cff94ef7f9b0ca489cbe43e1de737011476966e04c1578f4977177b0da217 ./uninstall.sh
Expand Down
2 changes: 2 additions & 0 deletions docs/REPOWISE.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ Update application reuses an external RepoWise executable when its version match

On Windows, reinstall also repairs a failed `repowise.exe` when the previous kit manifest records the fixed user-bin path.

Before Windows repair or upgrade, the installer checks for processes that use the selected RepoWise environment or launcher. It stops before replacement and lists matching process IDs. Close Codex sessions and stop RepoWise watchers before you rerun the command from a separate PowerShell window. The installer does not terminate processes. It reuses a healthy matching version without replacement.

The installer adds a user-level RepoWise MCP server. If Codex starts in a completely empty folder, the server command initializes Git first. It does not initialize Git in a non-empty folder.

The first start in a Git repository initializes a no-prose index when `.repowise` is absent. Each start then installs RepoWise's marker-delimited `post-commit` hook and catches up the index.
Expand Down
74 changes: 74 additions & 0 deletions docs/plans/CPK-068-windows-install.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Repair Windows installation safely


Windows installation must avoid replacing RepoWise files while its processes hold them open. The installer must identify those processes before it calls uv. After those processes stop, installation must repair the owned runtime and succeed. A healthy runtime must remain reusable. Reinstallation must preserve native plugin configuration, including tables that Codex inserts before a toolkit closing marker.

## Context and isolation


The task branch is `fix/windows-install-repowise`. It branches from `main` at `af509d29c353ed143fbd297e481e4bec1b61891e`. This task uses the existing checkout as one writable stream. Existing untracked `.mcp.json`, `.repowise/`, and `.vscode/` belong to the user.

The user reported a Node error during Ponytail installation, followed by a failed RepoWise repair. The second attempt failed when uv removed the Windows `Scripts` directory. Initial diagnosis found active RepoWise watcher and MCP processes in that environment, and RepoWise failed with `ModuleNotFoundError`. Node worked. A later installation exposed native Ponytail tables lost during marker replacement. The system Python also reports a library-prefix warning. Its configuration is outside this repository change.

The user authorized direct source access after RepoWise lookup failed. No Plan history or earlier task specification exists in this checkout.

Publish this bug fix as source version `0.26.1`. Keep the managed component versions unchanged.

## Product boundary and owners


The accepted source is the user's Windows installation report and instruction to continue. Existing product behavior is documented in `README.md` (Install and Ponytail) and `docs/REPOWISE.md` (Runtime ownership). Roadmap CPK-009 declines complete transactional recovery.

`install.ps1` dispatches to `kit.py`. The shared runtime owner is `ensure_repowise`, reached through `ensure_repowise_runtime` by installation, setup, and managed updates. It reads the kit manifest, checks the command version, and installs through uv with explicit tool and executable directories. These owners compose the change. The lock file, plugin installation, MCP bootstrap, and skill copying are opaque: preserve their current behavior. System Python installation is outside the change.

The existing marker writers also compose this task. Native Codex 0.163.0 inserts marketplace and plugin tables before the final comment in `config.toml`. That comment can be the RepoWise or hooks closing marker. Native plugin state remains opaque, but toolkit replacement and removal must preserve those unrelated tables. This direct reproduction expands the initial configuration boundary; it does not add a new plugin lifecycle.

Use [Scope boundaries](../../assets/skills/design-preflight/references/scope-boundaries.md) and [Supported model](../../assets/skills/design-preflight/references/supported-model.md). Active RepoWise processes and a second Windows installation attempt are explicitly included by this incident. Do not add automatic process termination, rollback, retries, component version changes, external packages, or a replacement runtime layout.

## Implementation and proof boundary


One atomic subtask, S1, belongs to `kit.py::ensure_repowise`. Before a Windows uv tool installation, use PowerShell 7 and `Get-CimInstance Win32_Process` to identify executable paths inside the selected RepoWise environment or at its exact user-bin launcher. Pass paths through the existing installation environment, not shell interpolation. Use a directory separator when matching the environment prefix. Compare paths without case sensitivity.

If any matching processes exist, raise `KitError` with their process IDs and instructions to close Codex sessions and stop RepoWise before retrying. Do not call uv or terminate processes. Without matches, preserve the existing install and shell-update sequence. A healthy matching version returns before the scan. The POSIX path remains unchanged. A failed process query must stop through the existing `run` error behavior.

Add direct regression checks in `tests/test_kit.py`. Execute the actual PowerShell script with a process-query fixture to prove path selection. Set `$ErrorActionPreference = 'Stop'` in that script. Update the Runtime ownership section of `docs/REPOWISE.md` and matching entries in `MANIFEST.sha256`. Complete validation and one full native review before any local task commit.

Extend that atomic subtask to the shared marker helpers, `marker_block` and `remove_marker_block`. For the two known TOML marker types, remove only the toolkit-owned table sections: `hooks` or `mcp_servers.repowise`. Preserve other table sections, even when Codex places them inside the textual markers. Append the replacement managed block after that preserved configuration. Generic Markdown marker behavior remains unchanged. Avoid a general TOML writer or new dependency; the section syntax comes from the toolkit and native Codex serializers.

Extend the native-command fixture in `tests/test_ponytail.py` to reproduce Codex's insertion location. Prove install, reinstall, and uninstall preserve marketplace/plugin tables and enabled state. Exercise the same owner through `apply-updates` in `tests/test_updates.py`. Add direct marker checks for unrelated tables interleaved with owned tables, including similarly named tables. Preserve native settings across repository setup and removal by using their shared marker owner.

## Boundary inventory and Scenario Proof


Apply [Scenario discrimination](../../assets/skills/design-preflight/references/scenario-discrimination.md). Sources have these dispositions: the Windows failure and safe repair requirement map to B1 and B2; existing healthy reuse and ownership map to B3; existing POSIX behavior maps to B4. Existing plugin and lock behavior are opaque. The prefix warning is diagnostic context, not a new installer boundary. Complete transaction recovery is deferred by CPK-009.

| Boundary | Discriminator and contrast | Production path and required oracle | Runnable check |
| --- | --- | --- | --- |
| B1: Detect active Windows RepoWise before mutation | Selected environment or exact launcher versus unrelated and sibling-prefix executable paths; zero, one, and multiple matches; equivalent paths with different case | `ensure_repowise` -> actual PowerShell script -> `KitError`; report all matching IDs, preserve runtime bytes, make no uv calls, and do not terminate processes | New `RepoWiseRuntimeTests.test_windows_repowise_repair_checks_process_paths` |
| B2: Repair after blockers stop | Same owned broken runtime with active records, then no matching records; query error versus successful empty query | `ensure_repowise` -> process query -> uv install -> shell update -> returned launcher; no install while blocked or when the query fails | New process-path check and `test_windows_repowise_process_query_errors_stop_install`, plus existing `test_install_repairs_manifest_owned_broken_windows_repowise` |
| B3: Preserve reuse and ownership | Healthy matching version versus broken owned and unowned commands | Existing version/manifest gates return healthy command or reject an unowned replacement before mutation | Existing matching-version and ownership tests in `RepoWiseRuntimeTests` |
| B4: Preserve POSIX behavior | POSIX selection versus Windows selection with the same broken owned runtime | Existing POSIX force install and shell update; no Windows process query | Existing `test_manifest_owned_broken_posix_repowise_is_repaired` |
| B5: Preserve native configuration while replacing toolkit TOML | Unrelated native tables inside versus outside the two managed spans; similar-prefix table versus owned table; repeated replacement and removal | Install/reinstall/update/uninstall/setup/remove-repo -> shared marker owner -> parsed configuration; retain every unrelated table and exact native settings. Core uninstall removes owned integration. Repository removal retains its required disabled RepoWise override | New marker preservation check, native fixture install/reinstall/uninstall tests, composed update check, and `IntegrationTests.test_setup_preserves_roadmap_and_unrelated_blocks` with native tables reseeded inside the marker before each entry point |

The Windows process query also retains the existing one-attempt error contract: a query error prevents uv execution. No retained state, background worker, or additional collection result exists. The process collection must include every matching ID; an unrelated prefix must not hide a later matching process.

Run the focused checks from the repository root:

python -B -m unittest discover -s tests -p test_kit.py -k RepoWiseRuntimeTests -v
python -B -m unittest discover -s tests -p test_ponytail.py -v

Run `pwsh -File .\run-tests.ps1` for the complete suite and syntax checks. Every applicable test must pass. Verify every listed `MANIFEST.sha256` digest. Store actual results in `.agent/test-results/CPK-068-windows-install.md`.

For local recovery, inspect the exact active RepoWise paths again, stop only the diagnosed broken RepoWise processes, and run the installer once. Verify RepoWise's version, ordinary reinstall, and Doctor. Use the already installed healthy uv-managed Python for diagnosis if the system Python warning interferes. Do not alter the system Python installation.

## Decision Log


Use a read-only process guard instead of automatic termination or a new versioned environment layout. Windows can hold executable files open; uv tool installation recreates environments. The existing uv owner remains responsible for installation. See [uv tool management](https://docs.astral.sh/uv/concepts/tools/).

Keep the native Ponytail lifecycle unchanged. Preserve its configuration through toolkit updates because a live reinstall exposed the marker-span deletion. Keep the system Python warning separate because the RepoWise environment already selects a uv-managed interpreter. OpenAI's [plugin documentation](https://developers.openai.com/plugins/build/plugins) identifies native marketplace commands and user-level plugin settings as their owners.

The independent preflight requires explicit case variation and terminating CIM errors. Accept both. Use a fixture at the native process-query seam to exercise the actual PowerShell selector without copying Python executables and DLLs. A separate local run against the diagnosed live processes proves native discovery, followed by one repair after those processes stop.

The configuration preflight requires explicit repository setup and removal proof because these paths share the changed marker owner. Reseed the native table inside the marker before each command. Preserve the repository removal disable override. This adds proof of existing behavior, not a new lifecycle.
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-068 — Repair Windows installation safely.** Detect active RepoWise processes before replacement, preserve native plugin configuration during reinstall, and restore the local installation.
- ✅ **CPK-067 — Keep current work lean and let Git own history.** Consolidate useful decisions, remove obsolete planning files and spent output, prevent capture replay, and validate without bytecode clutter.
- ✅ **CPK-066 — Correct runtime identity and install output.**
- ✅ **CPK-065 — Restore the requested finished-thread archival.**
Expand Down
41 changes: 39 additions & 2 deletions kit.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from typing import Any, Iterable

KIT_ID = "codex-practical-kit"
KIT_VERSION = "0.26.0"
KIT_VERSION = "0.26.1"
ROOT = Path(__file__).resolve().parent
AGENTS_START = "<!-- codex-practical-kit:start -->"
AGENTS_END = "<!-- codex-practical-kit:end -->"
Expand Down Expand Up @@ -189,6 +189,8 @@ def json_load(path: Path, default: Any) -> Any:


def marker_block(text: str, start: str, end: str, body: str) -> str:
if start in (HOOKS_START, REPOWISE_START):
text = remove_marker_block(text, start, end)
block = f"{start}\n{body.rstrip()}\n{end}"
pattern = re.compile(re.escape(start) + r".*?" + re.escape(end), re.DOTALL)
if pattern.search(text):
Expand All @@ -199,7 +201,20 @@ def marker_block(text: str, start: str, end: str, body: str) -> str:

def remove_marker_block(text: str, start: str, end: str) -> str:
pattern = re.compile(r"\n?" + re.escape(start) + r".*?" + re.escape(end) + r"\n?", re.DOTALL)
result = pattern.sub("\n", text, count=1)
def replacement(match: re.Match[str]) -> str:
if start not in (HOOKS_START, REPOWISE_START):
return "\n"
# Codex can insert unrelated TOML tables before a managed closing comment.
owner = "hooks" if start == HOOKS_START else "mcp_servers.repowise"
owned_header = re.compile(r"\[\[?" + re.escape(owner) + r"(?:\.|\])")
sections = re.split(r"(?m)(?=^\[)", match.group())
return "\n" + "".join(
section.replace(end, "")
for section in sections[1:]
if not owned_header.match(section)
)

result = pattern.sub(replacement, text, count=1)
result = re.sub(r"\n{3,}", "\n\n", result).strip()
return result + ("\n" if result else "")

Expand Down Expand Up @@ -1029,6 +1044,28 @@ def ensure_repowise(
env["HOME"] = str(paths.home)
env["UV_TOOL_BIN_DIR"] = str(destination)
env["UV_TOOL_DIR"] = str(paths.home / ".local" / "share" / "uv" / "tools")
if is_windows():
# Windows cannot replace a tool environment while its executables are running.
script = """
$ErrorActionPreference = 'Stop'
$toolPrefix = (Join-Path $env:UV_TOOL_DIR 'repowise') + [IO.Path]::DirectorySeparatorChar
$launcher = Join-Path $env:UV_TOOL_BIN_DIR 'repowise.exe'
@(Get-CimInstance Win32_Process | Where-Object {
$_.ExecutablePath -and (
$_.ExecutablePath.StartsWith($toolPrefix, [StringComparison]::OrdinalIgnoreCase) -or
$_.ExecutablePath.Equals($launcher, [StringComparison]::OrdinalIgnoreCase)
)
}).ProcessId -join ', '
"""
running = run(
["pwsh", "-NoProfile", "-NonInteractive", "-Command", script], env=env, timeout=30
).stdout.strip()
if running:
raise KitError(
f"RepoWise is still running (process IDs: {running}). "
"Close Codex sessions and stop RepoWise watchers, then rerun this command "
"from a separate PowerShell window. No RepoWise files were changed."
)
command = [uv, "tool", "install"]
if force:
command.append("--force")
Expand Down
Loading
Loading