Skip to content

Add Codex plugin integration - #418

Open
stevensuna wants to merge 7 commits into
umputun:masterfrom
stevensuna:feat/codex-integration
Open

Add Codex plugin integration#418
stevensuna wants to merge 7 commits into
umputun:masterfrom
stevensuna:feat/codex-integration

Conversation

@stevensuna

@stevensuna stevensuna commented Jul 22, 2026

Copy link
Copy Markdown

Summary

Add a repository-owned Codex plugin that exposes the existing Ralphex workflows as $ralphex, $ralphex-plan, $ralphex-adopt, and $ralphex-update through the repository marketplace and /skills discovery.

The Codex skills preserve the Claude workflows' plan selection, approval gates, output files, failure behavior, and status flow while adapting interaction and background-process handling to Codex-supported surfaces. Mandatory choices fall back to chat and stop for a real answer when the native question tool is unavailable or non-blocking. Skills are explicitly invoked only, and ralphex-adopt trusts only an installed revdiff launcher outside the target repository.

This also hardens the shared $ralphex launch contract. Claude and Codex users can select the configured executor or first-class --codex mode without confusing it with deprecated --codex-only/--external-only semantics. Review launches now fail closed unless the checkout is a clean non-default branch with a resolvable base and committed diff; repository-local executable overrides are rejected, positional plan paths are separated from flags, and launch confirmation requires both process state and fresh progress evidence.

Portable-plugin validation has its own make test-plugin target and CI step, separate from the Go-only make test. It validates marketplace paths, fixed package-local skill discovery, agents/openai.yaml, explicit-invocation policy, and symlink containment. The plugin version is independent from the Ralphex CLI version: maintainers bump all four manifests explicitly when distributed skills change, and a base-aware CI gate enforces that invariant. GoReleaser no longer mutates plugin manifests in its temporary release checkout.

Verification

Local repository checks passed on ba1bab3:

  • make test-plugin PLUGIN_VERSION_BASE=upstream/master — 10 portable-validator tests, 6 skill-contract tests, 8 version-gate tests, plus jq/sed updater fixtures
  • mise exec go@1.26.5 -- make test — race-enabled Go suite, 87.8% coverage
  • mise exec go@1.26.5 golangci-lint@2.11.4 -- make lint — 0 issues
  • git diff --check

External Codex system validators, not files shipped by this repository, accepted all four skills and the plugin package earlier in the branch. The two Codex skills changed by the final safety pass, $ralphex and $ralphex-adopt, were revalidated afterward.

Hosted CI checks are not currently reported by GitHub, and a fresh installed-plugin Codex invocation remains unverified locally.

@stevensuna
stevensuna requested a review from umputun as a code owner July 22, 2026 08:24
Port the existing Claude workflows to Codex-native skill, question, file, subagent, and background-session surfaces while preserving the standalone CLI contract. Keep the repository marketplace package aligned with Claude releases through the shared version updater.
@stevensuna
stevensuna force-pushed the feat/codex-integration branch from a01cf64 to ed834ca Compare July 23, 2026 07:55
@stevensuna stevensuna changed the title Add Codex integration skills Add Codex plugin integration Jul 23, 2026
stevensuna and others added 3 commits August 7, 2026 12:06
Add portable root discovery and normalize distributed Agent Skills frontmatter while retaining current Codex and Claude packaging. Extend the version updater and tests so all identities remain synchronized.

@umputun umputun left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

two blocking things, then a pile of smaller ones.

make test can't run without uv

Makefile:16 puts uv run --with pyyaml python scripts/validate-portable-plugin.py as the first line of the test target. Make stops at the first failing line, so without uv on PATH the target exits 127 and go test never runs. Nothing in the project provides or documents uv: CI calls go test directly and never make test, the Dockerfile installs python3 py3-pip only so ghcr.io/umputun/ralphex-go has no uv, and CONTRIBUTING.md, CLAUDE.md, README.md and llms.txt don't mention it. So CI stays green and the breakage only shows up on a contributor's machine, at CONTRIBUTING step 3. --with pyyaml also makes make test need the network on a cold cache.

pls give the validator its own target and call that from CI instead of sitting in front of go test. Swapping uv for python3 isn't a fix on its own: import yaml is inside the per-skill try/except at scripts/validate-portable-plugin.py:46, so a python3 without pyyaml reports all four skills as invalid YAML: No module named 'yaml', which is worse than uv: command not found.

codex only allows request_user_input in Plan mode

ModeKind::allows_request_user_input() is true for Plan only, and default_mode_request_user_input, the feature that would enable it in Default mode, is under development and off. A call from ordinary Code mode comes back with request_user_input is unavailable in Default mode. Even force-enabled it wouldn't help: is_blocking is mode == Plan, so outside Plan mode the ask doesn't block and can't gate anything. Checked against codex 0.147.0.

every mandatory choice in the ported skills goes through that tool with no fallback: $ralphex steps 2-4, including the confirmation before starting an autonomous run that edits code for hours; ralphex-adopt lines 259, 269 and 280, which cover the never-silent-overwrite decision on an existing plan file and the accept/revise/reject gate; ralphex-update 103 and 132. The Claude versions are fine here because AskUserQuestion is always available, so this is introduced by the port. The one chat fallback in the port, ralphex-adopt:99, is scoped to "more than 4 possibilities", not to the tool being unavailable.

each of those needs an explicit fallback: if the tool errors or isn't available, ask the same question in chat, end the turn, wait for a real answer, and never assume a default.

smaller ones

  • scripts/validate-portable-plugin.py:12 - ROOT.glob("**/.codex-plugin/plugin.json") descends into dot dirs, so it also validates plugin copies under .ralphex/worktrees/ and .claude/worktrees/. Both exist on disk in a working checkout. With a worktree open mid-edit on the plugin, make test in the clean main checkout fails naming paths from another branch. plugins/*/.codex-plugin/plugin.json is exact here.
  • scripts/validate-portable-plugin.py:46 - move import yaml to module scope so a missing pyyaml surfaces as an ImportError naming the dependency, instead of four bogus invalid YAML errors against valid frontmatter. Easy to hit since the shebang invites running the script directly.
  • llms.txt got no Codex section though README did. That's the file an LLM is pointed at for usage, so an agent reading it will tell a Codex user the integration doesn't exist. CLAUDE.md same story: plugins/ isn't in Project Structure, and Build Commands still calls make test plain "run tests with coverage".
  • CLAUDE.md:511 - the plugin-version rule names only assets/claude/, so a Codex-only skill fix triggers no version bump. Worth adding plugins/ralphex/skills/ as a trigger and both new manifests as things to bump.
  • assets/claude/skills/ralphex/SKILL.md:3 - argument-hint is gone but the skill still reads $ARGUMENTS at 29-31 and branches on "if no argument provided" at 46. The six-field frontmatter restriction applies to claude.ai uploads and the Skills API, not to plugin skills, and the new validator never reads assets/claude/, so dropping it loses the autocomplete hint for nothing.
  • plugins/ralphex/skills/ralphex-adopt/SKILL.md:246 - --wrap dropped from the revdiff call vs the Claude source. It's a plain revdiff flag with no Claude coupling, and without it the approval view opens unwrapped on plans with long lines.
  • scripts/internal/README.md:8 says the script updates three version fields. It updates four: both Claude manifests plus .codex-plugin/plugin.json and the portable plugin.json.

two design questions, not defects

  • plugins/ralphex/skills/ralphex-adopt/SKILL.md:244 runs the target repo's own scripts/launch-revdiff.sh when it exists. That's repo-controlled code executing with workspace permissions during what the user authorized as a plan conversion. Narrow, since it needs that exact path present, but I'd have the installed launcher win.
  • all four agents/openai.yaml set allow_implicit_invocation: true, so codex can fire the skills without $ralphex being typed. The project already fights this from the other direction: codexTaskGuidance in pkg/processor/prompts.go is injected into every --codex task prompt because an auto-activated skill can override the task prompt. Was implicit invocation deliberate?

btw the verification list mentions quick_validate.py and validate_plugin.py, neither is in the repo or in the diff.

Codex question prompts are not reliably blocking outside Plan mode, so mandatory choices now fall back to chat without assuming defaults. Ralphex Adopt also trusts only a canonical installed RevDiff launcher and accepts only the documented approval result pairs.

Keep Go tests independent of uv while validating the portable plugin explicitly in CI with pinned tooling. Tighten plugin discovery and align Codex/Claude documentation and invocation policy with the shipped workflows.
@stevensuna

Copy link
Copy Markdown
Author

The Default-mode question behavior and the launcher exit contract were the important catches here: the original port could continue without a blocking answer, and it treated stdout as approval without first validating the launcher result. I pushed 088357b to address the review.

Mandatory choices in all four Codex skills now fall back to the same question in chat, end the turn, and never assume a default when the native question tool is unavailable, errors, or does not block. The skills are also user-invoked only.

ralphex-adopt now uses only an installed launcher whose canonical path is outside the target repository. It accepts only exit 0 with empty stdout as approval and exit 10 with annotations as a revision; every other result falls back to the in-chat approval gate. --wrap is restored.

The portable validator has its own make test-plugin target and runs explicitly in CI with pinned uv/PyYAML versions. make test is Go-only again. Plugin discovery is limited to plugins/* and fails if it finds no plugin, while a missing PyYAML import now fails once at module load.

I also restored the Claude argument-hint, updated llms.txt and CLAUDE.md for both client surfaces and all four version fields, and corrected the internal script description.

Local checks passed: make test, make test-plugin, make lint, the version-update fixture, four external Codex skill validations, the external Codex plugin validator, and git diff --check. The PR body now identifies the two Codex system validators as external rather than repo-owned. Hosted CI and a fresh installed-plugin Codex runtime smoke have not run locally.

Review modes operate on the current checkout and may edit and commit findings, while stale progress files and option-like plan paths could make unsafe launches look valid.

Require a clean committed feature-branch diff, block repository-local executable overrides, separate positional plans from flags, and confirm fresh process progress. Align Claude and Codex skill mode semantics and bump the independently versioned plugin payload.
Static manifest validation alone could not detect stale plugin versions, unsafe package traversal, or drift in launch-safety instructions, and the release hook only mutated an ephemeral checkout.

Validate marketplace and agent metadata without following symlinks, add contract and negative tests, require a plugin version bump when distributed skills change, and keep CLI releases independent from plugin versioning.

@umputun umputun left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the Codex plugin part looks fine, nothing turned up in plugins/ralphex/** itself. One part should come out of this PR though, and then a handful of fixes on what's left.

split out the launch preflight. 0b8f4aaa adds ~115 lines of Step 6/Step 7 fail-closed gating to assets/claude/skills/ralphex/SKILL.md and its Codex twin. Nothing in adding a Codex plugin needs it. The Step 2/3 rework is genuine integration work, the skill offered "Codex-only" wired to a deprecated flag and knew nothing about --codex, but the preflight is untrusted-repo hardening that would apply the same if this PR didn't exist. My round-1 comment was narrower than that, just ralphex-adopt running the target repo's own scripts/launch-revdiff.sh, and 088357b4 fixed exactly that.

What couples them now is scripts/validate-skill-contracts_test.py, which asserts the same contract strings in both copies, so reverting the Claude one breaks make test-plugin. That test came in ba1bab32, in this PR, so it isn't a constraint that existed before.

pls move both to a separate PR. It needs its own discussion, because as written the gates refuse things ralphex supports:

  • blocking on an active claude_command, codex_command, custom_review_script or vcs_command in .ralphex/config, with no override, kills the skill for anyone who followed the docs. docs/custom-providers.md heads seven config blocks with # in ~/.config/ralphex/config or .ralphex/config, and docs/hg-support.md:32 tells hg users to put vcs_command there. It also doesn't look at whether the selected run would use the key, so picking the Codex executor still fails on an unused claude_command.
  • the clean-tree requirement covers Review pipeline and External review, which is exactly what modeRequiresBranch (cmd/ralphex/main.go:377) exempts from the uncommitted check, and what pkg/git/service.go:212 recommends as the way out of a dirty tree. ralphex --init leaves ?? .ralphex/config behind, so that alone blocks every review run.
  • the executable blocklist misses notify_custom_script (pkg/config/values.go:728, executed at pkg/notify/custom.go:28), and it doesn't look at .ralphex/prompts/*.txt or .ralphex/agents/*.txt at all. Those are repo-local overrides of the prompts driving an agent with a shell, a wider surface than any single script path.

what's left needs these:

blocking: allowed-tools is now a space-separated string - assets/claude/skills/ralphex/SKILL.md:5, same in ralphex-adopt/SKILL.md:4 and ralphex-update/SKILL.md:4. Claude Code takes a comma-separated string or an array, whitespace isn't a separator, so Bash Read AskUserQuestion TaskOutput Glob resolves to one pattern matching no tool and the skills get an empty tool set. /ralphex then can't run which ralphex, can't launch, can't tail the progress file. Nothing here forces the change: the allowed-tools must be a string rule in scripts/validate-portable-plugin.py:255 only reads plugins/ralphex/skills/, and none of the four Codex skills declare the field.

blocking: version gate dies on an unreachable base - scripts/internal/check-plugin-version.py:108 raises on any base it can't rev-parse. CI feeds it github.event.before for push events (.github/workflows/ci.yml:37), and that commit is dangling after any rebase or amend force-push, while fetch-depth: 0 fetches refs, not dangling objects. The step runs ahead of build and test in the same job, so the Go suite, the wrapper tests and lint all go dark on a branch that touched no skill files. Treating an unresolvable base like the zeros base fixes it, pull_request still has a real base.sha.

on the step order itself: the round-1 uv problem was fixed by splitting make test-plugin out so it couldn't gate go test, and putting that step first in the only job puts the coupling back. Worth running it after build and test, or in its own job.

nits:

  • CLAUDE.md:513 still says bump "on release" while .claude-plugin/README.md:21, scripts/internal/README.md:13 and CI all say before merge. That file is what agents read as authoritative for this repo, so it will send them the wrong way. make bump-plugin-version is also missing from the Build Commands block and from CONTRIBUTING.md:20, so nothing a contributor runs locally exercises the gate.
  • check-plugin-version.py:114 only rejects current == baseline, so a lower version passes as a bump. Takes a typo in make bump-plugin-version VERSION=, but nothing else in the repo enforces ordering.
  • scripts/validate-skill-contracts_test.py covers 3 of the 8 shipped skill docs, and doesn't pin the Interactive Choice Contract from 088357b4. Delete that paragraph from any of the four Codex skills and make test-plugin stays green.
  • validate-portable-plugin.py, validate-portable-plugin_test.py and validate-skill-contracts_test.py are maintainer-only but sit at scripts/ root, while check-plugin-version.py went to scripts/internal/. docs/plans/completed/20260313-scripts-reorg.md put internal tooling under internal/ and left root for user-facing things, and scripts/internal/README.md documents only the two that went there.
  • update-plugin-version_test.sh, the jq_fixture case runs the sed branch when jq is absent, so the suite reports both paths passing while only one ran.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants