mirror of
https://github.com/max-sixty/worktrunk.git
synced 2026-09-14 20:00:38 +08:00
codex/remove-codex-cloud-specific-tests
14 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
cfd6f099bc |
fix(codex): clear activity marker on session end (#3660)
Codex now exposes `SessionEnd`, but Worktrunk’s Codex plugin only returned the activity marker to idle at turn end. This adds a main-session exit hook that clears the marker, using Codex’s three-second maximum hook timeout so cleanup has the best chance to complete without delaying shutdown. The CLI help, plugin-layout guidance, user documentation, generated mirrors, metadata test, and help snapshot now describe and verify the complete Codex lifecycle. Tested with `cargo run -- hook pre-merge --yes` (4,564 tests passed; 1 skipped). > _This was written by Claude Code on behalf of max_. |
||
|
|
36ba57bdca |
fix(plugin): ship skills to Codex installs via a generated real-file mirror (#3440)
Codex installs of the worktrunk plugin carried no skills. `codex plugin add` copies the plugin into `$CODEX_HOME/plugins/cache/` via `copy_dir_recursive` (codex-rs core-plugins), which handles only regular files and directories — so the `skills -> ../../skills` symlink (and the nested `reference/README.md` link inside the tree) were silently dropped, and sessions load from that cache copy. Verified against codex-cli 0.144.1 both by reading the tagged sources and by installing this repo's plugin into a scratch `CODEX_HOME`: the installed root had no `skills/` at all and `codex debug prompt-input` showed an empty plugin skill inventory. This PR cuts the plugin's `skills` symlink over to a **generated real-file mirror** of the authored repo-root `skills/`, kept current by a new `sync_plugin_skills_mirror` stage in `test_docs_are_in_sync` (dereferences symlinks, deletes stale files, self-heals and fails on drift — same pattern as the other generated mirrors). Repo-root `skills/` stays the authored home: Gemini hard-probes it at the extension root, the docs sync writes into it, and Windows checkouts read it. Reversing the symlink direction instead would put a symlink at the repo root, breaking Gemini's install copy and every Windows checkout — which also surfaces a latent bug this fixes: symlinks materialize as plain text files on Windows clones, so installs from a Windows checkout shipped no skills to Claude or Codex either. It also drops the Codex manifest's `skills: "./skills/"` key: with no key, Codex scans `<plugin-root>/skills/` by convention (`default_skill_roots` in `codex-rs/core-plugins/src/loader.rs`), and the explicit path resolves to the same directory, so the key was redundant — symmetric with the Claude manifest cutover in #3431. **For the reviewer:** - The 20 files under `plugins/worktrunk/skills/` are the generated mirror (byte-identical to repo-root `skills/`; git stores shared blobs once). `plugins/worktrunk/CLAUDE.md` → "Plugin skills are a generated mirror" documents the rationale with codex-rs citations. - `sync_plugin_skills_mirror` in `tests/integration_tests/readme_sync.rs` is the sync stage (Step 3b of the pipeline); it already proved itself once in this branch — merging main regenerated `reference/{config,list}.md` in the mirror. - `test_plugin_layout_is_consolidated` now pins the mirror shape cross-platform (real directory, no symlinks anywhere under it), replacing the unix-only symlink assertion. **Verification:** fresh scratch-`CODEX_HOME` install now carries both skills into the cache and `codex debug prompt-input` lists `worktrunk` and `wt-switch-create`; `claude plugin validate` passes and `claude --plugin-dir … plugin details` discovers Skills (2) plus all 8 hooks; Gemini's repo-root path is untouched. A 2×2 probe matrix (manifest key present/absent × real dir/symlink) confirmed key-absence changes nothing and symlinks ship nothing. > _This was written by Claude Code on behalf of max_ --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0fc3518f8d |
docs(plugins): record the deliberate absence of a manifest version field (#3434)
Follow-up to #3431: records in `plugins/worktrunk/CLAUDE.md` why the Claude manifest deliberately omits a `version` field — installs pin the marketplace git SHA, so a semver would be a second version to maintain with nothing consuming it — so a future cleanup pass doesn't "fix" the `claude plugin validate` warning by adding one. > _This was written by Claude Code on behalf of max_ Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6c122a5d08 |
refactor(plugin): adopt Claude's canonical manifest layout, convention-only discovery (#3431)
## What this does Moves the Claude plugin manifest to the canonical `plugins/worktrunk/.claude-plugin/plugin.json` and strips it to metadata only, dropping the `hooks` and `skills` keys so that both component types load purely by Claude Code's convention discovery. Rewrites the layout docs and inverts `test_plugin_layout_is_consolidated` to pin the new invariants. ## Background The plugin layout has been in flux: [#3382](https://github.com/max-sixty/worktrunk/pull/3382) Claude-scoped the hooks filename, which silently broke hook loading ([#3417](https://github.com/max-sixty/worktrunk/issues/3417)), and [#3418](https://github.com/max-sixty/worktrunk/pull/3418) reverted it. That revert restored working hooks but left two claims baked into the layout, the docs, and the test that this PR found to be stale. ## Research method Everything below was verified empirically against claude-cli 2.1.207 (the current release), not from docs alone: - **Scratch marketplace probe**: a throwaway marketplace mirroring worktrunk's exact shape (repo-root `.claude-plugin/marketplace.json`, plugin in a `./plugins/<name>` subdir source, `skills` as a symlink pointing outside the plugin dir, hooks at the conventional path) with two plugin variants — manifest in `.claude-plugin/plugin.json` vs. bare `plugin.json` at the plugin root — installed through the real `claude plugin marketplace add` + `claude plugin install` flow. - **Component inventory**: `claude plugin details` and `claude plugin validate` on both variants. - **Live sessions**: headless `claude -p` runs confirming skills appear in-session and hook commands actually execute (marker files, and reading the 🤖 activity marker from inside a live session). - **Loader inspection**: the manifest resolution order read directly out of the claude-code binary's bundled loader code. ## Findings 1. **The "no `.claude-plugin/` wrapper" claim no longer holds.** The repo documented — and the test pinned — that Claude wants `plugin.json` bare at the plugin root, and that a `.claude-plugin/` wrapper in a subdir-source plugin fails with "Plugin not found". On 2.1.207 the wrapper form installs, loads skills, and fires hooks end-to-end. The loader tries `.claude-plugin/plugin.json` first with root `plugin.json` only as a runtime fallback, several marketplace-resolution paths read *only* the wrapper, and `claude plugin validate` rejects the root-level form outright ("No manifest found in directory. Expected .claude-plugin/marketplace.json or .claude-plugin/plugin.json"). 2. **"Claude has no skill auto-discovery" is wrong.** Skills are auto-discovered by scanning `skills/*/SKILL.md`; a `skills` manifest array only *adds* directories to that scan (entries equal to the default dir are filtered). An unlisted skill behind the out-of-plugin symlink loaded fine — the installer dereferences the symlink into a real directory in the install cache. Dropping the array also makes the old test guard ("every skill must be listed or it's silently invisible") structurally unnecessary: a new repo-root skill now ships to Claude, Codex, and Gemini with no manifest change anywhere. 3. **The `hooks` manifest key was dead config.** #3417 established the string-path override isn't honored for plugin loads; convention discovery of `hooks/hooks.json` is the mechanism that works. A key pointing at the conventional path adds nothing and could mask a future mislocated file, so it's gone (one mechanism per guarantee). ## Changes - `plugins/worktrunk/plugin.json` → `plugins/worktrunk/.claude-plugin/plugin.json`; content reduced to `name`/`description`/`author`. - `plugins/worktrunk/CLAUDE.md`: layout diagram and Claude path-resolution bullet rewritten to the convention-only model; the refuted wrapper claim and the stale no-auto-discovery claim removed. - Root `CLAUDE.md`: the Plugin Layout pointer updated (dropped the stale "Codex no-hooks re-enablement" phrase — Codex hooks shipped inline in #3364). - `test_plugin_layout_is_consolidated`: now asserts the wrapper manifest exists, a root-level `plugin.json` does not, the manifest carries no `hooks`/`skills` keys, the plugin `skills` symlink resolves to the repo-root `skills/` (unix only — Windows checkouts without `core.symlinks` materialize it as a file), and every repo-root skill dir carries a `SKILL.md` (the scan silently ignores a dir without one). Codex and Gemini are untouched: Codex reads only its `.codex-plugin/` wrapper, and its inline `hooks` object still overrides Codex's own convention discovery, so the #3362 collision stays closed independent of any of this. ## Verification - `claude plugin validate plugins/worktrunk` passes (it failed on the old layout). Remaining warnings are deliberate: no `version` field (installs pin the git SHA as the version; a hand-maintained semver would go stale) and the plugin-root `CLAUDE.md` (developer docs, not plugin context). - A live `claude --plugin-dir` session on this exact tree loads both skills and the `UserPromptSubmit` hook sets the 🤖 marker, observed from inside the session. - The scratch-marketplace probe covered the same shape through the full marketplace install flow, including the symlinked skills dir. - Full pre-merge gate: 4389 tests passed plus lints, doctests, and docs-sync. ## Risk The one scenario this could regress is a Claude Code version old enough to read *only* the root-level manifest. That couldn't be tested directly; the mitigations are that the wrapper is the documented convention, the loader prefers it today, and Claude Code self-updates aggressively. The old layout's "verified end-to-end" claim most likely dates from an earlier 2.1.x behavior or a misdiagnosed failure. > _This was written by Claude Code on behalf of max_ Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
69d6afb1fe |
fix: restore Claude plugin hooks to conventional hooks/hooks.json (#3418)
## Problem Since [#3382](https://github.com/max-sixty/worktrunk/pull/3382) ("Claude-scope the shared hooks payload"), the Claude plugin's hooks stopped loading in Claude Code. Skills and commands still load, so the plugin looks healthy — but `/hooks` shows none of worktrunk's handlers and the 🤖/💬 activity markers never update. It fails silently. Root cause (confirmed by the reporter on Claude Code 2.1.207): Claude Code discovers plugin hooks by **convention** at `hooks/hooks.json`. It does **not** honor `plugin.json`'s string-path `"hooks": "./hooks/claude-hooks.json"` override for plugin loads. The #3382 rename moved the file off the only path Claude's loader actually reads. The rename was intended to stop Codex's convention discovery from surfacing Claude's events ([#3362](https://github.com/max-sixty/worktrunk/issues/3362)). But the Codex manifest already defined its hooks **inline** (`Some(Inline)` branch of `resolve_manifest_hooks`), which overrides Codex's convention discovery — and that inline definition **predates** the rename (verified against `2677f05^`). So the rename was always redundant for the Codex scoping, while it broke Claude's discovery. ## Solution Restore the file to the conventional `plugins/worktrunk/hooks/hooks.json` and point `plugin.json`'s `hooks` back at it. `plugin.json` reverts byte-for-byte to its pre-#3382 state. This does not resurface the #3362 collision: the Codex manifest's inline `hooks` object keeps Codex off the shared `hooks/hooks.json` regardless of the filename. The two toolchains now coexist on one file — Claude discovers it, Codex ignores it via the inline override. The `test_plugin_layout_is_consolidated` assertions covering the Codex inline manifest are unchanged and still pass. Also updates `plugins/worktrunk/CLAUDE.md` (layout diagram + Known Limitations) and drops the now-reverted #3382 CHANGELOG entry, replacing it with a Fixed entry (both were in the unreleased 0.67.0, so the net change is nil). ## Testing Reproduction is captured in `test_plugin_layout_is_consolidated` (`tests/integration_tests/config_show.rs`), whose invariant was inverted from "the Claude file must be Claude-scoped" to "the Claude file must sit at the conventional `hooks/hooks.json` that Claude Code's loader discovers." Against pre-fix production it fails (`left: "./hooks/claude-hooks.json"`); after the rename it passes. - `test_plugin_layout_is_consolidated` ✅ - `test_claude_hook_commands_parse_in_all_shells` ✅ (reads the renamed file; shell-integration-tests) - Full `integration_tests::config_show` module: 136 passed ✅ - `test_docs_are_in_sync` ✅ **Not verified end-to-end from CI** (per running-tend "Don't ship fixes you can't verify"): the bot cannot drive a live Claude Code or Codex session to observe the hooks firing. The fix rests on the reporter's confirmed symptom + repro (symlinking `hooks/hooks.json` → `claude-hooks.json` immediately restores the hooks) and on Claude Code's [documented convention path](https://code.claude.com/docs/en/plugins-reference.md) (`hooks/hooks.json` in plugin root). The Codex-stays-clean claim rests on the existing code analysis in CLAUDE.md and the inline manifest predating the rename, not a live Codex run. --- Closes #3417 — automated triage --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> |
||
|
|
2677f05d0c | feat(plugin): Claude-scope the shared hooks payload (#3382) | ||
|
|
512a94de2f | feat(plugin): ship Codex-native activity hooks (#3364) | ||
|
|
33deef94c7 | docs: fix stale marker config key and bare-repo test comment (#3066) | ||
|
|
fd3ae5bff4 |
feat(plugin): optional branch name and single-route flow for /wt-switch-create (#3058)
The `/wt-switch-create` branch argument becomes optional (a name is
picked from the task when omitted), and the skill's workflow is rebuilt
from a guard-heavy three-route flow into one route plus one error-driven
fallback.
**The new flow**: pick a branch name if none was given, create the
worktree with `wt -C <repo> switch --create <branch> --no-cd
--format=json` in Bash (rerunning without `--create` when the user named
an existing branch), re-root the session with `EnterWorktree({path})`,
and on a graceful rejection (different repo, pinned cwd, nested session)
work in the worktree via absolute paths instead.
**Why the rewrite**: the old flow's guards encoded hypotheses about
Claude Code that turned out wrong or untested. The load-bearing
discoveries, each verified by binary inspection, official docs, or live
tests and documented in the new `skills/wt-switch-create/rationale.md`:
`EnterWorktree({path})` accepts worktrunk's sibling layout on first
entry; every rejection it can raise is side-effect-free, so
try-then-fallback replaces prediction; the previous
`EnterWorktree({name})`/hook route silently auto-removes a clean
worktree at session exit, which is wrong for durable worktrunk worktrees
(path-entered worktrees persist); and the "pinned cwd" the old guards
defended against was a misdiagnosis of the harness resetting `cd` that
leaves the session's working directories.
**Hook fix**: both `WorktreeCreate` and `WorktreeRemove` pipelines are
wrapped in `bash -c 'set -o pipefail; ...'`. Without it the trailing
`jq` exits 0 on empty input, so a `wt` failure surfaced as a
"successful" hook with an empty path. The wrapper is explicit `bash`
because hooks spawn via `/bin/sh -c`, where dash rejects `set -o
pipefail` fatally. `xargs -I{}` also fixes worktree paths containing
spaces. Tested end-to-end in both directions (success exits 0 with the
path; failure exits 1 with empty stdout).
Docs, README, and plugin CLAUDE.md are aligned with the new mechanism.
The agent-isolation use of the `WorktreeCreate` hook is unchanged.
Review trail: the design survived an evidence audit (every rationale
claim independently re-verified), a code review (bare directory-named
tokens no longer parse as the repo; mid-session moves now stash
uncommitted work across), and a level check that considered and rejected
fixing this in `wt` itself (an idempotent `--create` cannot encode who
chose the branch name, which determines the correct recovery).
> _This was written by Claude Code on behalf of max_
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
a35dd7f598 | fix(plugin): add waiting-for-input marker transitions for Claude hooks (#3023) | ||
|
|
6fd1bf4566 | Fix Claude hooks for Fish shell users. (#2962) | ||
|
|
1cc89d172e |
docs(claude): slim root CLAUDE.md, relocate detail to sibling files (#2816)
The root `CLAUDE.md` loads into every conversation, so its ~510 lines were paid on every session. This cuts it to ~190 lines without losing any documented behavior or invariant. Three things happened: derivable/stale content was removed (the JSON Output Format pointer, just a redirect to `wt list --help`), prose was tightened throughout, and task-specific detail was pushed down to the sibling `CLAUDE.md` that loads where that work happens — the root keeps a one-line pointer. **Relocations** (substance preserved at the destination, root keeps a pointer): - Testing commands, the `mock-stub` filtered-run gotcha, Claude Code web setup, the shell/PTY nextest SIGTTOU suspension, and codecov investigation mechanics → `tests/CLAUDE.md` - Doc sync taxonomy (the three categories + PRIMARY SOURCE), three-context help-text authoring, the `.gitattributes` `linguist-generated=false` exemption, config-TOML double-comment rule → `docs/CLAUDE.md` - Plugin layout, the Codex no-hooks re-enablement conditions, the accepted `wt-switch-create` tradeoff → `plugins/worktrunk/CLAUDE.md` (its back-pointers into the root were made self-contained so nothing dangles) - The add-a-CLI-command recipe → `src/commands/CLAUDE.md` **Kept in the root, tightened but substance intact:** the governing invariants — Data Safety, the Command Execution Principles (`shell_exec::Cmd`, structured output, network policy, signal handling), Project Commands Run Only After Approval (verbatim — the #2806 TOCTOU fix depends on its wording), the codecov merge gate, Config Deprecation, accessor naming, and the Worktree Model. A third commit consolidates the doc-sync explanation in `docs/CLAUDE.md`: the new "Doc sync taxonomy" section was overlapping with the file's existing "Command documentation" area on "edit `src/cli/mod.rs`" and `test_docs_are_in_sync`. Now the taxonomy is the single entry point and "Command page generation" reads as the category-1 mechanism details. Every CLAUDE.md heading quoted in a code comment is preserved verbatim so the breadcrumbs still resolve under a case-sensitive grep: `Plugin Layout` (`src/commands/config/codex.rs`), `Project Commands Run Only After Approval` (`src/commands/hook_plan.rs`, `src/commands/picker/mod.rs`), `Network Access` (`src/git/repository/config.rs`), and `Signal Handling` (`src/git/error.rs`, `src/commands/run_pipeline.rs`, `src/commands/command_executor.rs`). The first push downcased the latter two; the automated reviewer caught it and the second commit restored Title Case across all Command Execution Principles subheadings. `test_docs_are_in_sync` passes (docs-only change; no code touched). --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d7e33efc6c |
refactor(plugin): relocate Gemini extension to repo root (#2807)
Follow-up from a full review of the plugin implementation.
**The finding:** Gemini hard-probes `${extensionPath}/{hooks,skills}/`
at the extension root with no manifest path indirection, and `gemini
extensions install owner/repo` reads `gemini-extension.json` only at the
clone root. The `plugins/gemini/` sibling dir fought that — it needed a
`skills` symlink and a byte-identical bundled `wt.sh` purely to survive
`gemini extensions install`'s `fs.cp`, and github-URL install was broken
entirely (manifest invisible under a subdir). One install-fragile
artifact (`wt.sh`) was bundled to solve this; the other (`skills`) was
left a symlink that the guard test actively pinned — so the extension
shipped no skills to real users.
**The fix:** treat Gemini's manifest as the loader-mandated root pointer
it is — the third tool pointer alongside Claude's
(`.claude-plugin/marketplace.json`) and Codex's
(`.agents/plugins/marketplace.json`). With the manifest at the repo
root, `${extensionPath}` is the repo root, so `${extensionPath}/skills/`
resolves to the real single-sourced repo-root `skills/` and the hooks
call the canonical `${extensionPath}/plugins/worktrunk/hooks/wt.sh` by
its real path. This is net deletion: the symlink, the duplicated
`wt.sh`, and its drift guard all go away, and native `gemini extensions
install owner/repo` now works.
Verified with `gemini extensions validate .` against gemini-cli 0.42. A
true end-to-end `gemini extensions install` was not exercised — it
blocks on an interactive trust prompt — so the native-install claim
rests on documented Gemini behaviour plus `validate`, not a live
install.
Also hardens `test_plugin_layout_is_consolidated` (two other review
findings):
- Every repo-root `skills/` subdir must be listed in the Claude
`plugin.json` `skills` array. Claude has no skill auto-discovery
(confirmed against the official plugin reference), so an unlisted skill
is silently invisible to Claude while Codex/Gemini pick it up — this
turns that latent drift into a build failure.
- All four manifest descriptions (Claude ×2, Codex, Gemini) must share
the canonical product opening sentence. Byte-identical across all four
is schema-impossible (Codex omits the activity clause, Gemini says
"extension"), but the shared stem is a meaningful cross-tool drift
guard.
`CLAUDE.md` and `plugins/worktrunk/CLAUDE.md` updated: the repo root now
carries one loader-mandated pointer per tool (three, not two); tree
diagram, path-resolution rules, and the `wt-switch-create`
shared-`skills/` tradeoff all extended to Gemini.
Full pre-merge gate green locally: 3745 tests, 0 skipped; all lints; no
snapshot churn.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
d22917702e |
refactor(plugins): consolidate Claude + Codex into one payload dir (#2789)
The Claude plugin lived at the repo root (`.claude-plugin/`) while the
Codex plugin lived in `plugins/worktrunk/` — two homes for one logical
plugin, with the description string duplicated across both and drifting
independently. This collapses them into a single payload directory.
**What moved.** `git mv
.claude-plugin/{plugin.json,hooks/,CLAUDE.md,README.md} →
plugins/worktrunk/` (history-preserving renames). The repo root keeps
only the two marketplace pointers — Claude and Codex each hardcode their
marketplace path with no fallback, so two pointer files is the
irreducible floor. Both pointers' `source` now resolves to
`./plugins/worktrunk`. `.claude-plugin/` is now a single 2-line file.
**The load-bearing constraint** (verified live against claude-cli
2.1.x): for a *subdirectory* `source`, Claude expects `plugin.json` at
the plugin root **without** a `.claude-plugin/` wrapper — that wrapper
is marketplace-root-only. (First attempt with the wrapper failed `Plugin
not found`; the corrected layout installs cleanly.) Codex keeps its own
required `.codex-plugin/` wrapper. So inside `plugins/worktrunk/`:
`plugin.json` is Claude's, `.codex-plugin/plugin.json` is Codex's,
`hooks/` is Claude's, `skills → ../../skills` is shared.
**Path edits inside moved files:** `plugin.json` `hooks` →
`./hooks/hooks.json`; `hooks.json`
`${CLAUDE_PLUGIN_ROOT}/.claude-plugin/hooks/wt.sh` →
`${CLAUDE_PLUGIN_ROOT}/hooks/wt.sh`.
**Reviewer navigation:** `git log` shows the moves as renames.
`plugins/worktrunk/CLAUDE.md` was rewritten to document the unified
layout + per-tool path resolution; the repo `CLAUDE.md` "Plugin Layout"
section likewise. `tests/integration_tests/config_show.rs` adds
`test_plugin_layout_is_consolidated`, which locks the layout invariants
(`.claude-plugin/` is marketplace-only, Claude manifest at plugin root,
hooks relative to it) **and** asserts the duplicated Claude description
stays byte-identical across the marketplace pointer and the manifest —
that was the standing follow-up; JSON can't `include!`, so a drift test
is the right tool, not a generator.
**Verification:** Live end-to-end on both real CLIs, twice — once with
copied skill dirs, once mirroring the real tree *including the
`plugins/worktrunk/skills` symlink* (Claude's `skills` array resolves
through it). Full pre-merge gate green: 3720 passed, 0 skipped, all
pre-commit lints, no snapshot churn. No user-facing docs/help text
changed (the consolidation is repo-internal), so `test_docs_are_in_sync`
needed no resync.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|