claude2codex 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PATTERNS.md +21 -0
- package/PATTERNS.zh-CN.md +21 -0
- package/README.md +41 -10
- package/README.zh-CN.md +41 -10
- package/c2c.skill.md +58 -0
- package/dist/server.js +94 -32
- package/package.json +5 -3
package/PATTERNS.md
CHANGED
|
@@ -73,6 +73,27 @@ And the meta-rule for the handoff: **require it in the contract.** Every contrac
|
|
|
73
73
|
|
|
74
74
|
A missing or incomplete handoff is itself a review failure — you never have to argue about it, because it was a success condition.
|
|
75
75
|
|
|
76
|
+
## The objective budget: your success conditions have a character limit
|
|
77
|
+
|
|
78
|
+
The contract you send and the objective the goal loop audits are **not the same string**. `codex_implement` renders a compact objective from your `goal` plus every `success_condition` and sets it via `thread/goal/set`. That objective — not the full contract prompt — is what Codex's `/goal` loop checks before declaring the goal achieved. Constraints and context files never reach it; they live only in the turn prompt.
|
|
79
|
+
|
|
80
|
+
It has a hard budget: `GOAL_OBJECTIVE_MAX`, default **2000 characters**, of which ~213 are fixed rendering boilerplate. The remaining ~1787 are your goal text plus the concatenation of every success condition.
|
|
81
|
+
|
|
82
|
+
Two consequences:
|
|
83
|
+
|
|
84
|
+
- **Prose burns the budget fast.** A ten-condition contract of ordinary English sentences lands around 1700–2000 characters. The contract that added this very check measured 1987/2000 on its first draft — 13 characters of margin, entirely by accident.
|
|
85
|
+
- **Measure before you send.** `codex_estimate` reports `objective_chars`, `objective_max_chars`, and `objective_over_limit` without creating a job. Over-limit calls to `codex_implement` are rejected at submit time — no job, no process, no tokens spent — with the actual count in the error.
|
|
86
|
+
|
|
87
|
+
Write success conditions as assertions, not paragraphs:
|
|
88
|
+
|
|
89
|
+
```markdown
|
|
90
|
+
Bad (124 chars): The implementation should make sure that whenever a contract
|
|
91
|
+
exceeds the configured limit, the tool refuses to create a job.
|
|
92
|
+
Good (76 chars): Over-limit contracts return isError and create no job (state dir unchanged).
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
If a task genuinely needs a longer objective, raise `GOAL_OBJECTIVE_MAX` deliberately. The usual right answer is that ten crisp conditions beat six verbose ones — and the crisp ones review better anyway.
|
|
96
|
+
|
|
76
97
|
## Review: never trust, always re-run
|
|
77
98
|
|
|
78
99
|
The handoff says `bun test` passed. Run `bun test` anyway. In this repo's run every handoff happened to be truthful — and that is exactly what you can't assume, because the day one isn't, the re-run is your only defense. The review loop that worked:
|
package/PATTERNS.zh-CN.md
CHANGED
|
@@ -72,6 +72,27 @@ codex_implement 必须支持声明对先前 job 的依赖:被依赖的 job 成
|
|
|
72
72
|
|
|
73
73
|
缺失或不完整的 handoff 本身就是审查失败——你永远不需要为此争论,因为它就是一条成功条件。
|
|
74
74
|
|
|
75
|
+
## objective 预算:你的 Success Conditions 有字符上限
|
|
76
|
+
|
|
77
|
+
你发出去的合同,和 goal loop 实际审的 objective,**不是同一个字符串**。`codex_implement` 会从 `goal` 加上每一条 `success_condition` 渲染出一个精简的 objective,通过 `thread/goal/set` 设进去。Codex 的 `/goal` 循环在宣布目标达成前,审的是这个 objective,而不是完整的合同 prompt。Constraints 和 context files 根本进不去 —— 它们只存在于 turn prompt 里。
|
|
78
|
+
|
|
79
|
+
它有一条硬预算:`GOAL_OBJECTIVE_MAX`,默认 **2000 字符**,其中约 213 是固定的渲染样板。剩下约 1787 字符由你的 goal 正文和所有 success conditions 拼接而成。
|
|
80
|
+
|
|
81
|
+
两个后果:
|
|
82
|
+
|
|
83
|
+
- **散文最烧预算。** 十条普通英文句子写成的 success conditions,落点大约在 1700–2000 字符。加入这条检查的那份合同,初稿实测 1987/2000 —— 只剩 13 字符余量,纯属侥幸。
|
|
84
|
+
- **发之前先量。** `codex_estimate` 会返回 `objective_chars`、`objective_max_chars`、`objective_over_limit`,且不创建 job。超限的 `codex_implement` 调用会在提交时被拒 —— 不建 job、不起进程、不烧 token —— 错误里直接给出实际字符数。
|
|
85
|
+
|
|
86
|
+
Success conditions 要写成断言,不要写成段落:
|
|
87
|
+
|
|
88
|
+
```markdown
|
|
89
|
+
差(124 字符):The implementation should make sure that whenever a contract
|
|
90
|
+
exceeds the configured limit, the tool refuses to create a job.
|
|
91
|
+
好( 76 字符):Over-limit contracts return isError and create no job (state dir unchanged).
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
如果某个任务确实需要更长的 objective,那就有意识地调高 `GOAL_OBJECTIVE_MAX`。但通常正确的答案是:十条精悍的条件胜过六条啰嗦的 —— 而且精悍的那种审查起来也更省事。
|
|
95
|
+
|
|
75
96
|
## 审查:永不轻信,永远重跑
|
|
76
97
|
|
|
77
98
|
handoff 说 `bun test` 过了。你还是要自己跑一遍 `bun test`。在本仓库的实战中,每一份 handoff 碰巧都是诚实的——而这恰恰是你不能假设的事,因为哪天有一份不诚实,你的重跑就是唯一的防线。行之有效的审查循环:
|
package/README.md
CHANGED
|
@@ -4,14 +4,23 @@ An MCP server that lets Claude Code delegate implementation work to OpenAI Codex
|
|
|
4
4
|
|
|
5
5
|
*[中文版](./README.zh-CN.md)*
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Demo
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
One real contract round-trip, recorded live: Claude drafts the Goal Contract, `codex_estimate` prices it, Codex implements under its goal loop while `codex_status` streams progress, and Claude independently re-verifies the handoff.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- **OpenAI Codex** — the implementer. Receives specifications, writes code, runs tests, reports back.
|
|
11
|
+

|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
*Real Codex run (13k tokens, 50s), waits compressed. Replay in your terminal: `asciinema play demo/demo.cast`*
|
|
14
|
+
|
|
15
|
+
## Why
|
|
16
|
+
|
|
17
|
+
In 2025, AI-assisted coding meant writing detailed prompts that told the model *how* to implement things — which files to edit, what patterns to follow, sometimes nearly pseudocode. The engineer was a director; the AI was a typist that needed constant guidance.
|
|
18
|
+
|
|
19
|
+
That has changed. In 2026, models are [strong enough at implementation](https://www.anthropic.com/institute/recursive-self-improvement) that micromanaging the how is no longer the best use of a human's time. The bottleneck has shifted: when implementation is cheap, the expensive mistakes are **defining the wrong problem** and **failing to catch a bad result**. Getting the implementation steps right matters far less than getting the goal right.
|
|
20
|
+
|
|
21
|
+
This is why Goal Contracts are structured the way they are. They don't explain how to write the code — they define **what the code must do** (the Goal) and **how to prove it worked** (Success Conditions). The engineer's role becomes problem definition, context provision, and verification — the same things that make a good engineering manager effective at delegation.
|
|
22
|
+
|
|
23
|
+
This project is built on that premise: split planning and review (where human judgment still dominates) from implementation (where models now excel), and connect them with a contract format that forces clarity on the parts that matter most.
|
|
15
24
|
|
|
16
25
|
```mermaid
|
|
17
26
|
flowchart LR
|
|
@@ -22,6 +31,10 @@ flowchart LR
|
|
|
22
31
|
Claude -.->|Delta Contract if review fails| Codex
|
|
23
32
|
```
|
|
24
33
|
|
|
34
|
+
- **You** — decide what to build and give final approval.
|
|
35
|
+
- **Claude Code** — the architect and reviewer. Defines the problem, writes the contract, verifies the result.
|
|
36
|
+
- **OpenAI Codex** — the implementer. Receives the contract, writes code, runs tests, reports back.
|
|
37
|
+
|
|
25
38
|
## What is a Goal Contract?
|
|
26
39
|
|
|
27
40
|
A Goal Contract is a structured specification that tells Codex *exactly* what to build. It has three parts:
|
|
@@ -82,15 +95,17 @@ flowchart TD
|
|
|
82
95
|
|
|
83
96
|
## Three-Layer Prompt Architecture
|
|
84
97
|
|
|
85
|
-
|
|
98
|
+
The prompt Codex works from is assembled from three layers, but only two of them come from claude2codex:
|
|
86
99
|
|
|
87
100
|
1. **Protocol layer** (embedded in the MCP server) — universal rules: how to self-verify work, how to format the handoff, how to handle rework. These never change regardless of project.
|
|
88
101
|
|
|
89
|
-
2. **Project layer** (
|
|
102
|
+
2. **Project layer** (`AGENTS.md`) — project-specific coding conventions, toolchain instructions, architectural constraints. **Loaded by Codex itself, not by this server.** Codex discovers `AGENTS.md` files across the working directory's repository tree (plus `AGENTS.override.md`) and places them in its instructions layer, subject to its own `project_doc_max_bytes` budget. claude2codex used to inject the file a second time and no longer does.
|
|
90
103
|
|
|
91
104
|
3. **Task layer** (the Goal/Delta Contract itself) — the specific work to be done this time.
|
|
92
105
|
|
|
93
|
-
This separation means the MCP server works out of the box (layer 1 is always there),
|
|
106
|
+
This separation means the MCP server works out of the box (layer 1 is always there), inherits whatever project context Codex already loads (layer 2), and carries the unique task specification (layer 3).
|
|
107
|
+
|
|
108
|
+
Note that the contract prompt and the **thread-goal objective** are different strings. The objective — rendered from `goal` plus every `success_condition` — is what Codex's `/goal` loop audits before declaring the goal achieved, and it has its own `GOAL_OBJECTIVE_MAX` budget. See [PATTERNS.md](PATTERNS.md#the-objective-budget-your-success-conditions-have-a-character-limit).
|
|
94
109
|
|
|
95
110
|
## Installation
|
|
96
111
|
|
|
@@ -115,6 +130,22 @@ Add to your Claude Code MCP settings:
|
|
|
115
130
|
}
|
|
116
131
|
```
|
|
117
132
|
|
|
133
|
+
### Skill (optional)
|
|
134
|
+
|
|
135
|
+
The package includes a Claude Code Skill that teaches Claude the full contract workflow — drafting Goal Contracts, delegating to Codex, reviewing handoffs, and reworking failures. Install it to use `/c2c` from any project:
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
mkdir -p ~/.claude/skills
|
|
139
|
+
cp "$(npm root -g)/claude2codex/c2c.skill.md" ~/.claude/skills/c2c.md
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Or download directly:
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
mkdir -p ~/.claude/skills
|
|
146
|
+
curl -fsSL https://raw.githubusercontent.com/PrestoOverture/c2c/main/c2c.skill.md -o ~/.claude/skills/c2c.md
|
|
147
|
+
```
|
|
148
|
+
|
|
118
149
|
## Tools Provided
|
|
119
150
|
|
|
120
151
|
| Tool | Description |
|
|
@@ -123,7 +154,7 @@ Add to your Claude Code MCP settings:
|
|
|
123
154
|
| `codex_status` | Check job progress: state, queue position, dependency, turns, goal status, token usage, time since last activity, transcript. |
|
|
124
155
|
| `codex_result` | Fetch the finished job's structured handoff. |
|
|
125
156
|
| `codex_rework` | Resume the Codex thread with a Delta Contract (also accepts `context_files`). |
|
|
126
|
-
| `codex_estimate` | Read-only pre-job cost estimate: rendered-prompt size plus token statistics from your local completed jobs. |
|
|
157
|
+
| `codex_estimate` | Read-only pre-job cost estimate: rendered-prompt size, thread-goal objective size against `GOAL_OBJECTIVE_MAX`, plus token statistics from your local completed jobs. |
|
|
127
158
|
| `codex_config` | Read-only view of Codex's current model, version, and settings. |
|
|
128
159
|
|
|
129
160
|
Jobs are persisted (`C2C_STATE_DIR`), so status, results, and rework survive server restarts. Jobs beyond the concurrency cap queue FIFO; a stall watchdog reports when a running job stops making progress. See [PATTERNS.md](./PATTERNS.md) for field-tested guidance on writing contracts that pass review on the first try.
|
|
@@ -151,7 +182,7 @@ Codex then enters its goal-continuation loop: after each turn, it checks "did I
|
|
|
151
182
|
| `CODEX_PERMISSIONS` | Unset | Permissions profile passed to Codex |
|
|
152
183
|
| `CODEX_JOB_TIMEOUT_MS` | `1800000` | Maximum job duration (30 min) |
|
|
153
184
|
| `CODEX_QUIET_MS` | `30000` | Quiet period before treating an active goal as finished |
|
|
154
|
-
| `GOAL_OBJECTIVE_MAX` | `2000` | Maximum goal-objective
|
|
185
|
+
| `GOAL_OBJECTIVE_MAX` | `2000` | Maximum rendered goal-objective length; `codex_implement` rejects over-limit contracts at submit time |
|
|
155
186
|
| `C2C_STATE_DIR` | `~/.claude2codex/jobs` | Persistent job registry (atomic per-job JSON files) |
|
|
156
187
|
| `C2C_LOG_LEVEL` | `info` | Structured JSON logs on stderr: `silent`, `error`, `info`, `debug` |
|
|
157
188
|
| `C2C_RETRIES` | `1` | Automatic retries for process failures before the first turn starts |
|
package/README.zh-CN.md
CHANGED
|
@@ -4,14 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
*[English](./README.md)*
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 演示
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
一次完整的workflow实践过程:Claude 起草 Goal Contract,`codex_estimate` 预估成本,Codex 在 goal loop 下自主实现(`codex_status` 实时流式进度),最后 Claude 独立复核 handoff。
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- **OpenAI Codex** — 实现者。接收规格说明,编写代码,运行测试,报告结果。
|
|
11
|
+

|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
*Codex 运行(13k tokens,50 秒),等待时间已压缩。终端回放:`asciinema play demo/demo.cast`*
|
|
14
|
+
|
|
15
|
+
## 为什么做这个项目
|
|
16
|
+
|
|
17
|
+
2025 年,AI 辅助编程的主流方式是写详细的 prompt 告诉模型*怎么*实现——改哪些文件、用什么模式、有时候几乎是伪代码。工程师是导演,AI 是需要不断指导的执行者。
|
|
18
|
+
|
|
19
|
+
这已经变了。2026 年,模型的[实现能力已经足够强](https://www.anthropic.com/institute/recursive-self-improvement),事无巨细地指挥"怎么写"不再是人最好的时间投入方式。瓶颈转移了:当实现变得廉价,真正昂贵的错误是**定义了错误的问题**和**没有验收出坏的结果**。把实现步骤写对,远不如把目标定义对重要。
|
|
20
|
+
|
|
21
|
+
这就是为什么 Goal Contract 被设计成这样。它不解释怎么写代码——它定义**代码必须做到什么**(Goal)和**怎么证明做对了**(Success Conditions)。工程师的角色变成了问题定义、上下文补充和验收——和一个好的工程管理者委派任务时做的事情一样。
|
|
22
|
+
|
|
23
|
+
这个项目建立在这个前提上:把规划和审查(人的判断力仍然占主导)与实现(模型已经足够强)分开,用一种契约格式把它们连接起来,迫使工程师在最重要的部分(问题定义和验收标准)上做到清晰。
|
|
15
24
|
|
|
16
25
|
```mermaid
|
|
17
26
|
flowchart LR
|
|
@@ -22,6 +31,10 @@ flowchart LR
|
|
|
22
31
|
Claude -.->|Delta Contract 修正| Codex
|
|
23
32
|
```
|
|
24
33
|
|
|
34
|
+
- **你** — 决定做什么,给最终批准。
|
|
35
|
+
- **Claude Code** — 架构师和审查者。定义问题,撰写契约,验证结果。
|
|
36
|
+
- **OpenAI Codex** — 实现者。接收契约,编写代码,运行测试,报告结果。
|
|
37
|
+
|
|
25
38
|
## 什么是 Goal Contract(目标契约)?
|
|
26
39
|
|
|
27
40
|
Goal Contract 是一份结构化规格说明,精确告诉 Codex **要做什么**。它由三部分组成:
|
|
@@ -82,15 +95,17 @@ flowchart TD
|
|
|
82
95
|
|
|
83
96
|
## 三层 Prompt 架构
|
|
84
97
|
|
|
85
|
-
|
|
98
|
+
Codex 最终工作时的 prompt 由三层组成,但只有两层来自 claude2codex:
|
|
86
99
|
|
|
87
100
|
1. **协议层**(内嵌在 MCP 服务器中)— 通用规则:如何自我验证、如何格式化交接报告、如何处理返工。无论什么项目都一样。
|
|
88
101
|
|
|
89
|
-
2.
|
|
102
|
+
2. **项目层**(`AGENTS.md`)— 项目特定的编码规范、工具链说明、架构约束。**由 Codex 自己加载,不是本服务器注入的。** Codex 会在工作目录所属的仓库树中查找 `AGENTS.md`(以及 `AGENTS.override.md`),放进它自己的 instructions 层,并受其 `project_doc_max_bytes` 预算约束。claude2codex 过去会重复注入一遍,现已移除。
|
|
90
103
|
|
|
91
104
|
3. **任务层**(Goal/Delta Contract 本身)— 这次要做的具体工作。
|
|
92
105
|
|
|
93
|
-
这种分离意味着:MCP 服务器开箱即用(第 1
|
|
106
|
+
这种分离意味着:MCP 服务器开箱即用(第 1 层始终存在),直接继承 Codex 已经加载的项目上下文(第 2 层),每次任务都有独特的规格说明(第 3 层)。
|
|
107
|
+
|
|
108
|
+
注意:合同 prompt 和 **thread-goal objective** 是两个不同的字符串。objective 由 `goal` 加上每一条 `success_condition` 渲染而成,是 Codex `/goal` 循环在宣布目标达成前实际审查的对象,并有自己的 `GOAL_OBJECTIVE_MAX` 预算。详见 [PATTERNS.zh-CN.md](PATTERNS.zh-CN.md#objective-预算你的-success-conditions-有字符上限)。
|
|
94
109
|
|
|
95
110
|
## 安装
|
|
96
111
|
|
|
@@ -115,6 +130,22 @@ npx claude2codex
|
|
|
115
130
|
}
|
|
116
131
|
```
|
|
117
132
|
|
|
133
|
+
### Skill(可选)
|
|
134
|
+
|
|
135
|
+
包内附带一个 Claude Code Skill,教 Claude 完整的契约工作流——起草 Goal Contract、委派给 Codex、审查 handoff、失败时返工。安装后可在任何项目中使用 `/c2c`:
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
mkdir -p ~/.claude/skills
|
|
139
|
+
cp "$(npm root -g)/claude2codex/c2c.skill.md" ~/.claude/skills/c2c.md
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
或直接下载:
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
mkdir -p ~/.claude/skills
|
|
146
|
+
curl -fsSL https://raw.githubusercontent.com/PrestoOverture/c2c/main/c2c.skill.md -o ~/.claude/skills/c2c.md
|
|
147
|
+
```
|
|
148
|
+
|
|
118
149
|
## 提供的工具
|
|
119
150
|
|
|
120
151
|
| 工具 | 说明 |
|
|
@@ -123,7 +154,7 @@ npx claude2codex
|
|
|
123
154
|
| `codex_status` | 查看任务进度:状态、队列位置、依赖、轮次、目标状态、token 用量、距上次活动的时间、活动记录。 |
|
|
124
155
|
| `codex_result` | 获取已完成任务的结构化交接报告。 |
|
|
125
156
|
| `codex_rework` | 用 Delta Contract 恢复 Codex 线程进行返工(同样支持 `context_files`)。 |
|
|
126
|
-
| `codex_estimate` | 只读的任务前成本预估:渲染后的 prompt
|
|
157
|
+
| `codex_estimate` | 只读的任务前成本预估:渲染后的 prompt 规模、thread-goal objective 相对 `GOAL_OBJECTIVE_MAX` 的规模,以及本机已完成任务的 token 统计。 |
|
|
127
158
|
| `codex_config` | 只读查看 Codex 当前模型、版本和配置。 |
|
|
128
159
|
|
|
129
160
|
任务会持久化(`C2C_STATE_DIR`),因此状态查询、结果获取和返工在 server 重启后依然可用。超出并发上限的任务按 FIFO 排队;停滞看门狗会在运行中的任务失去进展时主动上报。关于如何写出"一次过审"的合同,参见实战指南 [PATTERNS.zh-CN.md](./PATTERNS.zh-CN.md)。
|
|
@@ -151,7 +182,7 @@ npx claude2codex
|
|
|
151
182
|
| `CODEX_PERMISSIONS` | 未设置 | 权限配置透传 |
|
|
152
183
|
| `CODEX_JOB_TIMEOUT_MS` | `1800000` | 单任务最大时长(30 分钟) |
|
|
153
184
|
| `CODEX_QUIET_MS` | `30000` | 判定活跃目标已完成前的静默等待时间 |
|
|
154
|
-
| `GOAL_OBJECTIVE_MAX` | `2000` |
|
|
185
|
+
| `GOAL_OBJECTIVE_MAX` | `2000` | 渲染后 goal-objective 的最大长度;超限的合同会被 `codex_implement` 在提交时拒绝 |
|
|
155
186
|
| `C2C_STATE_DIR` | `~/.claude2codex/jobs` | 持久化任务注册表(每任务一个原子写入的 JSON 文件) |
|
|
156
187
|
| `C2C_LOG_LEVEL` | `info` | stderr 上的结构化 JSON 日志:`silent`、`error`、`info`、`debug` |
|
|
157
188
|
| `C2C_RETRIES` | `1` | 首个 turn 开始前的进程故障自动重试次数 |
|
package/c2c.skill.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: c2c
|
|
3
|
+
description: Delegate implementation to Codex via Goal Contracts. Draft, send, poll, review, rework.
|
|
4
|
+
trigger: When the user wants to delegate implementation work to Codex, or types /c2c.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the **architect and reviewer**. Codex is the **implementer**. You draft contracts, delegate via the `codex` MCP server, and review the output. You do not implement the task yourself.
|
|
8
|
+
|
|
9
|
+
If `codex_config` errors, tell the user to install the MCP server (npm: `claude2codex`).
|
|
10
|
+
|
|
11
|
+
## Workflow
|
|
12
|
+
|
|
13
|
+
1. **User decides** what to build or fix.
|
|
14
|
+
2. **You draft a Goal Contract** (format below). Show it to the user for approval.
|
|
15
|
+
3. **You send it** — `codex_estimate` to check budget, then `codex_implement`.
|
|
16
|
+
4. **You poll** — `codex_status` until terminal. Report progress to the user.
|
|
17
|
+
5. **You review** — `codex_result` for the handoff, then verify independently (see Review below).
|
|
18
|
+
6. **User approves** your findings.
|
|
19
|
+
|
|
20
|
+
If review fails → draft a **Delta Contract** → `codex_rework` (same `job_id`, thread resumes) → re-enter at step 4.
|
|
21
|
+
|
|
22
|
+
## Goal Contract
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
### Goal
|
|
26
|
+
What the code must do. First sentence = acceptance test in prose.
|
|
27
|
+
|
|
28
|
+
### Constraints
|
|
29
|
+
Only what Codex would get wrong without being told. One line of
|
|
30
|
+
"only modify files needed for this task" covers the rest.
|
|
31
|
+
|
|
32
|
+
### Success Conditions
|
|
33
|
+
- [ ] Assertions, not paragraphs. At least one is a command whose exit code decides.
|
|
34
|
+
- [ ] Handoff includes: Changed Files, Validation, Success Conditions, Risks & Deviations.
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Write lean contracts.** Goal and Success Conditions are what Codex acts on. Constraints are for genuine risks only — don't front-load your review checklist into the contract.
|
|
38
|
+
|
|
39
|
+
## Delta Contract
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
### Findings
|
|
43
|
+
- What is wrong, with file/line references.
|
|
44
|
+
|
|
45
|
+
### Failed Success Conditions
|
|
46
|
+
- [ ] The specific conditions that did not pass.
|
|
47
|
+
|
|
48
|
+
### Constraints
|
|
49
|
+
- Original constraints still apply. Fix only the findings; do not touch work that passed.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Review
|
|
53
|
+
|
|
54
|
+
1. Handoff must be complete (all four sections). Missing handoff = review failure.
|
|
55
|
+
2. Re-run every verification command yourself — do not trust the handoff's claims.
|
|
56
|
+
3. Run the project's own typecheck/build/test/lint.
|
|
57
|
+
4. Flag security issues and constraint violations.
|
|
58
|
+
5. Report findings to the user. Fix small issues (typos, imports) directly; do not rewrite the implementation.
|
package/dist/server.js
CHANGED
|
@@ -9,8 +9,6 @@ import { z } from "zod";
|
|
|
9
9
|
|
|
10
10
|
// src/contracts.ts
|
|
11
11
|
import { statSync } from "fs";
|
|
12
|
-
import { readFile } from "fs/promises";
|
|
13
|
-
import { join } from "path";
|
|
14
12
|
var PROTOCOL_INSTRUCTIONS = `## Protocol Instructions
|
|
15
13
|
|
|
16
14
|
### Execution Rules
|
|
@@ -42,14 +40,6 @@ End every task with exactly these sections:
|
|
|
42
40
|
|
|
43
41
|
### Rework
|
|
44
42
|
For a Delta Contract, fix only its review findings and failed Success Conditions. Do not revisit work that passed review. Repeat the full Self-Verification and Required Handoff.`;
|
|
45
|
-
async function optionalAgentsInstructions(cwd) {
|
|
46
|
-
try {
|
|
47
|
-
return await readFile(join(cwd, "AGENTS.md"), "utf8");
|
|
48
|
-
} catch (error) {
|
|
49
|
-
if (error?.code === "ENOENT") return void 0;
|
|
50
|
-
throw error;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
43
|
function renderContextFiles(contextFiles2) {
|
|
54
44
|
if (!contextFiles2?.length) return [];
|
|
55
45
|
return [
|
|
@@ -63,11 +53,9 @@ function renderContextFiles(contextFiles2) {
|
|
|
63
53
|
""
|
|
64
54
|
];
|
|
65
55
|
}
|
|
66
|
-
|
|
67
|
-
const agents = await optionalAgentsInstructions(cwd);
|
|
56
|
+
function renderGoalContract(c) {
|
|
68
57
|
const lines = [
|
|
69
58
|
PROTOCOL_INSTRUCTIONS,
|
|
70
|
-
...agents ? ["", "## Project Instructions", "", agents.trim()] : [],
|
|
71
59
|
"",
|
|
72
60
|
"## Goal Contract",
|
|
73
61
|
"",
|
|
@@ -102,10 +90,13 @@ function renderDeltaContract(d) {
|
|
|
102
90
|
];
|
|
103
91
|
return lines.join("\n");
|
|
104
92
|
}
|
|
105
|
-
function
|
|
93
|
+
function renderObjectiveDetails(c, maxLen) {
|
|
106
94
|
const conditions = c.success_conditions.map((x) => `(${x})`).join(" ");
|
|
107
95
|
const text = `Fulfill this Goal Contract: ${c.goal.trim()} The goal is achieved only when every Success Condition is verified: ${conditions} and a structured handoff (Changed Files / Validation / Success Conditions / Risks & Deviations) has been delivered.`;
|
|
108
|
-
return
|
|
96
|
+
return {
|
|
97
|
+
objective: text.length <= maxLen ? text : text.slice(0, maxLen - 1) + "\u2026",
|
|
98
|
+
objectiveChars: text.length
|
|
99
|
+
};
|
|
109
100
|
}
|
|
110
101
|
var HANDOFF_SECTIONS = [
|
|
111
102
|
"Changed Files",
|
|
@@ -146,6 +137,11 @@ var CodexAppServer = class {
|
|
|
146
137
|
stderrTail = [];
|
|
147
138
|
exitError;
|
|
148
139
|
exited = false;
|
|
140
|
+
/**
|
|
141
|
+
* Spawns a new Codex child process and sets up event listeners for stdout and stderr streams.
|
|
142
|
+
*
|
|
143
|
+
* @param opts - Configuration options specifying executable path, arguments, environment, and event callbacks
|
|
144
|
+
*/
|
|
149
145
|
constructor(opts) {
|
|
150
146
|
this.opts = opts;
|
|
151
147
|
this.proc = spawn(opts.bin, opts.args, {
|
|
@@ -167,6 +163,12 @@ var CodexAppServer = class {
|
|
|
167
163
|
this.handleExit(null, new Error(`failed to spawn ${opts.bin}: ${err.message}`));
|
|
168
164
|
});
|
|
169
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Handles Codex child process termination or spawn failure, rejecting pending requests and notifying listeners.
|
|
168
|
+
*
|
|
169
|
+
* @param code - Exit code of the child process, or null if terminated abnormally
|
|
170
|
+
* @param error - Error instance causing process exit, if any (optional)
|
|
171
|
+
*/
|
|
170
172
|
handleExit(code, error) {
|
|
171
173
|
if (this.exited) return;
|
|
172
174
|
this.exited = true;
|
|
@@ -181,6 +183,11 @@ var CodexAppServer = class {
|
|
|
181
183
|
this.pending.clear();
|
|
182
184
|
this.opts.onExit?.(code, this.exitError);
|
|
183
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Parses a single output line received from Codex stdout and dispatches it as a request response or notification.
|
|
188
|
+
*
|
|
189
|
+
* @param line - Raw text line emitted by the child process stdout
|
|
190
|
+
*/
|
|
184
191
|
handleLine(line) {
|
|
185
192
|
const trimmed = line.trim();
|
|
186
193
|
if (!trimmed) return;
|
|
@@ -208,10 +215,23 @@ var CodexAppServer = class {
|
|
|
208
215
|
this.opts.onNotification?.(msg.method, msg.params);
|
|
209
216
|
}
|
|
210
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Serializes an object to JSON and writes it to the child process's standard input stream.
|
|
220
|
+
*
|
|
221
|
+
* @param obj - Object to send to the child process
|
|
222
|
+
*/
|
|
211
223
|
send(obj) {
|
|
212
224
|
if (this.exited) return;
|
|
213
225
|
this.proc.stdin.write(JSON.stringify(obj) + "\n");
|
|
214
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Sends a correlated JSON-RPC request to Codex and returns a Promise that resolves with the response or times out.
|
|
229
|
+
*
|
|
230
|
+
* @param method - RPC method name to invoke
|
|
231
|
+
* @param params - Optional parameter object passed with the request
|
|
232
|
+
* @param timeoutMs - Maximum time to wait for a response in milliseconds, defaulting to 60,000ms
|
|
233
|
+
* @returns Promise resolving with the response result
|
|
234
|
+
*/
|
|
215
235
|
request(method, params, timeoutMs = 6e4) {
|
|
216
236
|
if (this.exited) {
|
|
217
237
|
return Promise.reject(this.exitError ?? new Error("codex app-server is not running"));
|
|
@@ -226,17 +246,31 @@ var CodexAppServer = class {
|
|
|
226
246
|
this.send({ id, method, ...params !== void 0 ? { params } : {} });
|
|
227
247
|
});
|
|
228
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* Sends a one-way notification message to Codex without waiting for a response.
|
|
251
|
+
*
|
|
252
|
+
* @param method - Notification method name
|
|
253
|
+
* @param params - Optional parameter object passed with the notification
|
|
254
|
+
*/
|
|
229
255
|
notify(method, params) {
|
|
230
256
|
this.send({ method, ...params !== void 0 ? { params } : {} });
|
|
231
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Sends the initial handshake request to Codex and emits the initialized notification upon response.
|
|
260
|
+
*
|
|
261
|
+
* @returns Promise resolving with the server initialization response payload
|
|
262
|
+
*/
|
|
232
263
|
async initialize() {
|
|
233
264
|
const result = await this.request("initialize", {
|
|
234
|
-
clientInfo: { name: "claude2codex", title: "Claude\u2194Codex Contract Bridge", version: "0.3.
|
|
265
|
+
clientInfo: { name: "claude2codex", title: "Claude\u2194Codex Contract Bridge", version: "0.3.1" },
|
|
235
266
|
capabilities: { experimentalApi: true }
|
|
236
267
|
});
|
|
237
268
|
this.notify("initialized", {});
|
|
238
269
|
return result;
|
|
239
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* Gracefully terminates the running Codex child process via SIGTERM signal.
|
|
273
|
+
*/
|
|
240
274
|
kill() {
|
|
241
275
|
if (!this.exited) this.proc.kill("SIGTERM");
|
|
242
276
|
}
|
|
@@ -258,7 +292,7 @@ function writeLog(level, event, fields = {}) {
|
|
|
258
292
|
// src/store.ts
|
|
259
293
|
import { accessSync, constants, mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from "fs";
|
|
260
294
|
import { homedir } from "os";
|
|
261
|
-
import { join
|
|
295
|
+
import { join } from "path";
|
|
262
296
|
var MAX_JOBS = 50;
|
|
263
297
|
var warned = false;
|
|
264
298
|
function warnOnce(error) {
|
|
@@ -269,7 +303,7 @@ function warnOnce(error) {
|
|
|
269
303
|
});
|
|
270
304
|
}
|
|
271
305
|
function defaultStateDir() {
|
|
272
|
-
return process.env.C2C_STATE_DIR ??
|
|
306
|
+
return process.env.C2C_STATE_DIR ?? join(homedir(), ".claude2codex", "jobs");
|
|
273
307
|
}
|
|
274
308
|
function createJobStore(dir = defaultStateDir()) {
|
|
275
309
|
const records = /* @__PURE__ */ new Map();
|
|
@@ -281,7 +315,7 @@ function createJobStore(dir = defaultStateDir()) {
|
|
|
281
315
|
for (const name of readdirSync(dir)) {
|
|
282
316
|
if (!name.endsWith(".json")) continue;
|
|
283
317
|
try {
|
|
284
|
-
const job = JSON.parse(readFileSync(
|
|
318
|
+
const job = JSON.parse(readFileSync(join(dir, name), "utf8"));
|
|
285
319
|
if (!job?.id || !job?.startedAt || !Array.isArray(job.transcript)) continue;
|
|
286
320
|
if (job.state === "blocked" || job.state === "queued" || job.state === "starting" || job.state === "running") {
|
|
287
321
|
job.state = "error";
|
|
@@ -300,8 +334,24 @@ function createJobStore(dir = defaultStateDir()) {
|
|
|
300
334
|
warnOnce(error);
|
|
301
335
|
}
|
|
302
336
|
return {
|
|
337
|
+
/**
|
|
338
|
+
* Retrieves a job from memory by its unique identifier.
|
|
339
|
+
*
|
|
340
|
+
* @param id - Unique job identifier to look up
|
|
341
|
+
* @returns The matching Job object, or undefined if not found
|
|
342
|
+
*/
|
|
303
343
|
getJob: (id) => records.get(id),
|
|
344
|
+
/**
|
|
345
|
+
* Returns a list of all currently cached jobs.
|
|
346
|
+
*
|
|
347
|
+
* @returns Array of cached Job objects
|
|
348
|
+
*/
|
|
304
349
|
listJobs: () => [...records.values()],
|
|
350
|
+
/**
|
|
351
|
+
* Persists job state and transcript history to memory cache and atomic disk storage.
|
|
352
|
+
*
|
|
353
|
+
* @param job - Job object to be saved
|
|
354
|
+
*/
|
|
305
355
|
save(job) {
|
|
306
356
|
const record = { ...job, transcript: job.transcript.slice(-200) };
|
|
307
357
|
records.set(job.id, record);
|
|
@@ -317,8 +367,8 @@ function createJobStore(dir = defaultStateDir()) {
|
|
|
317
367
|
};
|
|
318
368
|
}
|
|
319
369
|
function atomicWrite(dir, job) {
|
|
320
|
-
const target =
|
|
321
|
-
const temp =
|
|
370
|
+
const target = join(dir, `${job.id}.json`);
|
|
371
|
+
const temp = join(dir, `.${job.id}.${process.pid}.${Date.now()}.tmp`);
|
|
322
372
|
writeFileSync(temp, JSON.stringify(job), { encoding: "utf8", mode: 384 });
|
|
323
373
|
renameSync(temp, target);
|
|
324
374
|
}
|
|
@@ -329,7 +379,7 @@ function prune(dir, records) {
|
|
|
329
379
|
for (const job of entries.slice(MAX_JOBS)) {
|
|
330
380
|
records.delete(job.id);
|
|
331
381
|
try {
|
|
332
|
-
unlinkSync(
|
|
382
|
+
unlinkSync(join(dir, `${job.id}.json`));
|
|
333
383
|
} catch (error) {
|
|
334
384
|
if (error?.code !== "ENOENT") throw error;
|
|
335
385
|
}
|
|
@@ -852,6 +902,7 @@ function config(cwd) {
|
|
|
852
902
|
};
|
|
853
903
|
}
|
|
854
904
|
var OBJECTIVE_MAX = envInt("GOAL_OBJECTIVE_MAX", 2e3);
|
|
905
|
+
var objectiveBudgetDescription = `The rendered thread-goal objective has a GOAL_OBJECTIVE_MAX budget of ${OBJECTIVE_MAX} characters; over-limit calls are rejected.`;
|
|
855
906
|
function jobSummary(job, transcriptTail = 15) {
|
|
856
907
|
return {
|
|
857
908
|
job_id: job.id,
|
|
@@ -879,7 +930,7 @@ function textResult(obj, isError = false) {
|
|
|
879
930
|
isError
|
|
880
931
|
};
|
|
881
932
|
}
|
|
882
|
-
var server = new McpServer({ name: "c2c-codex", version: "0.3.
|
|
933
|
+
var server = new McpServer({ name: "c2c-codex", version: "0.3.1" });
|
|
883
934
|
var reasoningEffort = z.enum(["low", "medium", "high", "xhigh", "max", "ultra"]);
|
|
884
935
|
var contextFiles = z.array(z.object({
|
|
885
936
|
path: z.string().min(1),
|
|
@@ -946,9 +997,9 @@ server.registerTool(
|
|
|
946
997
|
title: "Delegate a Goal Contract to Codex",
|
|
947
998
|
description: "Start a Codex implementation job for a Goal Contract. Sets the contract as a Codex thread goal (the /goal loop keeps Codex iterating until the objective is achieved or the token budget is hit) and sends the full contract as the first turn. Returns a job_id immediately; poll codex_status, then fetch codex_result when done. Language-agnostic: verification commands come from the contract's Success Conditions, not from this server.",
|
|
948
999
|
inputSchema: {
|
|
949
|
-
goal: z.string().min(1).describe(
|
|
1000
|
+
goal: z.string().min(1).describe(`What the code must do when this task is done. ${objectiveBudgetDescription}`),
|
|
950
1001
|
constraints: z.array(z.string()).describe("Technical boundaries: files to touch, patterns to follow, 'Do not modify' lists."),
|
|
951
|
-
success_conditions: z.array(z.string()).min(1).describe(
|
|
1002
|
+
success_conditions: z.array(z.string()).min(1).describe(`Checkable criteria proving the goal is met. At least one must be a runnable command/test. ${objectiveBudgetDescription}`),
|
|
952
1003
|
token_budget: z.number().int().positive().optional().describe("Optional token budget for the Codex goal loop (thread/goal/set tokenBudget)."),
|
|
953
1004
|
cwd: z.string().optional().describe("Working directory for Codex. Defaults to the project directory."),
|
|
954
1005
|
context_files: contextFiles.optional().describe("Files or directories Codex should read before implementing."),
|
|
@@ -966,13 +1017,19 @@ server.registerTool(
|
|
|
966
1017
|
success_conditions: input.success_conditions,
|
|
967
1018
|
context_files: resolvedContext.files
|
|
968
1019
|
};
|
|
969
|
-
const prompt =
|
|
1020
|
+
const prompt = renderGoalContract(contract);
|
|
970
1021
|
const dependencyError = dependencyValidationError(input.depends_on);
|
|
971
1022
|
if (dependencyError) return textResult({ error: dependencyError }, true);
|
|
1023
|
+
const renderedObjective = renderObjectiveDetails(contract, OBJECTIVE_MAX);
|
|
1024
|
+
if (renderedObjective.objectiveChars > OBJECTIVE_MAX) {
|
|
1025
|
+
return textResult({
|
|
1026
|
+
error: `Rendered objective is ${renderedObjective.objectiveChars} characters, exceeding the GOAL_OBJECTIVE_MAX limit of ${OBJECTIVE_MAX}. Shorten goal or success_conditions, or raise GOAL_OBJECTIVE_MAX.`
|
|
1027
|
+
}, true);
|
|
1028
|
+
}
|
|
972
1029
|
const job = startJob({
|
|
973
1030
|
kind: "implement",
|
|
974
1031
|
prompt,
|
|
975
|
-
objective:
|
|
1032
|
+
objective: renderedObjective.objective,
|
|
976
1033
|
tokenBudget: input.token_budget,
|
|
977
1034
|
contextFiles: resolvedContext.files?.map((file) => file.path),
|
|
978
1035
|
dependsOn: input.depends_on,
|
|
@@ -993,10 +1050,10 @@ server.registerTool(
|
|
|
993
1050
|
title: "Estimate a Goal Contract's token cost",
|
|
994
1051
|
description: "Read-only estimate made before starting a job. Measures the fully rendered Goal Contract prompt and returns approx_prompt_tokens using an approximate characters/4 heuristic, plus local completed-job history.",
|
|
995
1052
|
inputSchema: {
|
|
996
|
-
goal: z.string().min(1).describe(
|
|
1053
|
+
goal: z.string().min(1).describe(`What the code must do when this task is done. ${objectiveBudgetDescription}`),
|
|
997
1054
|
constraints: z.array(z.string()).describe("Technical boundaries: files to touch, patterns to follow, 'Do not modify' lists."),
|
|
998
|
-
success_conditions: z.array(z.string()).min(1).describe(
|
|
999
|
-
cwd: z.string().optional().describe("Working directory used to
|
|
1055
|
+
success_conditions: z.array(z.string()).min(1).describe(`Checkable criteria proving the goal is met. At least one must be a runnable command/test. ${objectiveBudgetDescription}`),
|
|
1056
|
+
cwd: z.string().optional().describe("Working directory used to resolve relative context_files paths. Defaults to the project directory."),
|
|
1000
1057
|
context_files: contextFiles.optional().describe("Files or directories included in the rendered prompt.")
|
|
1001
1058
|
},
|
|
1002
1059
|
annotations: { readOnlyHint: true }
|
|
@@ -1005,16 +1062,21 @@ server.registerTool(
|
|
|
1005
1062
|
const jobConfig = config(input.cwd);
|
|
1006
1063
|
const resolvedContext = await resolveContextFiles(input.context_files, jobConfig.cwd);
|
|
1007
1064
|
if (resolvedContext.missing.length) return missingContextFiles(resolvedContext.missing);
|
|
1008
|
-
const
|
|
1065
|
+
const contract = {
|
|
1009
1066
|
goal: input.goal,
|
|
1010
1067
|
constraints: input.constraints,
|
|
1011
1068
|
success_conditions: input.success_conditions,
|
|
1012
1069
|
context_files: resolvedContext.files
|
|
1013
|
-
}
|
|
1070
|
+
};
|
|
1071
|
+
const prompt = renderGoalContract(contract);
|
|
1072
|
+
const renderedObjective = renderObjectiveDetails(contract, OBJECTIVE_MAX);
|
|
1014
1073
|
const history = implementHistory();
|
|
1015
1074
|
return textResult({
|
|
1016
1075
|
prompt_chars: prompt.length,
|
|
1017
1076
|
approx_prompt_tokens: Math.ceil(prompt.length / 4),
|
|
1077
|
+
objective_chars: renderedObjective.objectiveChars,
|
|
1078
|
+
objective_max_chars: OBJECTIVE_MAX,
|
|
1079
|
+
objective_over_limit: renderedObjective.objectiveChars > OBJECTIVE_MAX,
|
|
1018
1080
|
history,
|
|
1019
1081
|
estimated_total_tokens: history?.median_total_tokens ?? null,
|
|
1020
1082
|
note: history ? "Estimated total tokens are based on local history and vary with task complexity." : "No completed implement job history is available; estimates based on local history vary with task complexity."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude2codex",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP server bridging Claude (architect/reviewer) and Codex (implementer) via Goal/Delta Contracts and Codex's thread-goal loop.",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
11
|
"PATTERNS.md",
|
|
12
|
-
"PATTERNS.zh-CN.md"
|
|
12
|
+
"PATTERNS.zh-CN.md",
|
|
13
|
+
"c2c.skill.md"
|
|
13
14
|
],
|
|
14
15
|
"keywords": [
|
|
15
16
|
"mcp",
|
|
@@ -30,7 +31,8 @@
|
|
|
30
31
|
"start": "bun src/server.ts",
|
|
31
32
|
"build": "tsup",
|
|
32
33
|
"test": "bun test",
|
|
33
|
-
"prepack": "cp ../README.md ../README.zh-CN.md ../PATTERNS.md ../PATTERNS.zh-CN.md ."
|
|
34
|
+
"prepack": "cp ../README.md ../README.zh-CN.md ../PATTERNS.md ../PATTERNS.zh-CN.md ../c2c.skill.md .",
|
|
35
|
+
"postpack": "rm -f README.md README.zh-CN.md PATTERNS.md PATTERNS.zh-CN.md c2c.skill.md"
|
|
34
36
|
},
|
|
35
37
|
"dependencies": {
|
|
36
38
|
"@modelcontextprotocol/sdk": "^1.29.0",
|