odd-studio 3.2.3 → 3.3.1

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "odd-studio",
3
3
  "description": "Outcome-Driven Development — a planning and build harness for domain experts building serious software with AI. Installs the /odd skill, safety hooks, and project scaffolding into Claude Code.",
4
- "version": "3.2.2",
4
+ "version": "3.3.1",
5
5
  "author": {
6
6
  "name": "ODD Studio"
7
7
  },
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  A planning and build harness for domain experts who are building serious software with AI — and want to understand exactly what they're doing and why.
6
6
 
7
- Works with **Claude Code** and **OpenCode** (with Ollama, Qwen, DeepSeek, MiniMax, and 75+ other model providers).
7
+ Works with **Claude Code**, **OpenCode**, and **Codex**.
8
8
 
9
9
  ---
10
10
 
@@ -38,21 +38,38 @@ npx odd-studio init my-project --agent opencode
38
38
  cd my-project
39
39
  ```
40
40
 
41
- ### For both agents on the same machine
41
+ ### For Codex
42
+
43
+ ```bash
44
+ npx odd-studio init my-project --agent codex
45
+ cd my-project
46
+ ```
47
+
48
+ In Codex, ODD Studio is installed as a project-local plugin with visible plugin commands. After restarting Codex, type `/odd`.
49
+ Once inside `/odd`, the same starred flow works there too, including commands like `*plan`, `*build`, and `*status`. You can also invoke the direct Codex commands such as `/odd-build`.
50
+
51
+ ### For Claude Code and OpenCode on the same machine
42
52
 
43
53
  ```bash
44
54
  npx odd-studio init my-project --agent both
45
55
  cd my-project
46
56
  ```
47
57
 
58
+ ### For every supported agent on the same machine
59
+
60
+ ```bash
61
+ npx odd-studio init my-project --agent all
62
+ cd my-project
63
+ ```
64
+
48
65
  Everything is installed into your project folder. Nothing is written to your home directory or installed globally.
49
66
 
50
67
  That single command:
51
68
 
52
69
  - Detects your installed AI coding agent (or uses `--agent` to specify)
53
70
  - Scaffolds your project structure (`docs/`, `.odd/`, instruction files)
54
- - Installs the `/odd` skill and safety hooks into the project (`.claude/`)
55
- - Configures odd-flow MCP server for cross-session memory (`.mcp.json`)
71
+ - Installs the ODD harness into the matching project-local agent surface (`.claude/`, `.opencode/`, or `plugins/odd-studio/`)
72
+ - Configures odd-flow MCP server for cross-session memory in the matching agent config
56
73
  - Optionally installs Checkpoint security scanning (you'll be asked)
57
74
  - Initialises git with an initial commit
58
75
 
@@ -69,8 +86,9 @@ That single command:
69
86
  |-------|-------------------|---------------|
70
87
  | **Claude Code** | Project-local skills (`.claude/skills/`), hooks (`.claude/settings.local.json`), odd-flow MCP (`.mcp.json`) | Claude (Opus, Sonnet, Haiku) |
71
88
  | **OpenCode** | Project-local commands (`.opencode/commands/`), JS plugin (`.opencode/plugins/`), odd-flow MCP (`opencode.json`) | Any provider — Ollama (Qwen3-Coder, MiniMax M2.7, DeepSeek), OpenAI, Anthropic, Google, Groq, and 75+ more |
89
+ | **Codex** | Project-local plugin (`plugins/odd-studio/`), local marketplace registration (`.agents/plugins/marketplace.json`), plugin MCP (`plugins/odd-studio/.mcp.json`) | Codex |
72
90
 
73
- Both agents get the same methodology, the same safety enforcement, and the same odd-flow-powered cross-session memory. The only difference is the delivery mechanism.
91
+ All supported agents get the same methodology, the same safety enforcement, and the same odd-flow-powered cross-session memory. The only difference is the delivery mechanism.
74
92
 
75
93
  ---
76
94
 
@@ -138,6 +156,8 @@ ODD Studio installs safety gates that run automatically throughout your build. T
138
156
 
139
157
  **Claude Code:** Implemented as shell hooks registered in `.claude/settings.local.json` (project-local).
140
158
  **OpenCode:** Implemented as a JS plugin (`odd-studio-plugin.js`) in `.opencode/plugins/` (project-local).
159
+ **Codex:** Implemented as a project-local plugin in `plugins/odd-studio/` with `hooks.json` and plugin-local skills.
160
+ It also installs Codex plugin commands so `/odd` is directly discoverable in the composer.
141
161
 
142
162
  ---
143
163
 
@@ -150,7 +170,7 @@ When you're ready to build, ODD Studio initialises a odd-flow swarm — a team o
150
170
  - **UI agent** — implements the frontend using shadcn/ui, Tailwind CSS v4, and Framer Motion, against WCAG 2.1 AA accessibility standards
151
171
  - **QA agent** — runs your verification steps and reports failures in your language, not technical error messages
152
172
 
153
- odd-flow MCP is configured automatically in your project's `.mcp.json` it provides cross-session memory for both Claude Code and OpenCode. Every agent knows the full project state, regardless of which sessions they were spawned in.
173
+ odd-flow MCP is configured automatically in your project-local agent config — `.mcp.json` for Claude Code, `opencode.json` for OpenCode, and `plugins/odd-studio/.mcp.json` for Codex. Every agent knows the full project state, regardless of which sessions they were spawned in.
154
174
 
155
175
  ---
156
176
 
@@ -206,8 +226,8 @@ After `npx odd-studio init my-project`, your project looks like this:
206
226
  ```
207
227
  my-project/
208
228
  ├── CLAUDE.md ← ODD build rules (Claude Code)
209
- ├── AGENTS.md ← ODD build rules (OpenCode)
210
- ├── .mcp.json ← odd-flow MCP config (project-local)
229
+ ├── AGENTS.md ← ODD build rules (OpenCode and Codex)
230
+ ├── .mcp.json ← odd-flow MCP config (Claude Code)
211
231
  ├── .odd/
212
232
  │ └── state.json ← Project state (updated automatically)
213
233
  ├── .claude/ ← Claude Code config (project-local)
@@ -219,6 +239,14 @@ my-project/
219
239
  │ ├── commands/ ← /odd command files
220
240
  │ ├── plugins/odd-studio-plugin.js ← Safety gate plugin
221
241
  │ └── odd/ ← Skill knowledge base
242
+ ├── .agents/plugins/marketplace.json ← Codex local marketplace registration
243
+ ├── plugins/odd-studio/ ← Codex project-local plugin
244
+ │ ├── .codex-plugin/plugin.json ← Codex plugin manifest
245
+ │ ├── commands/odd.md ← Codex /odd command entrypoint
246
+ │ ├── skills/ ← ODD Studio skills for Codex
247
+ │ ├── hooks/odd-studio.sh ← Safety gate hook script
248
+ │ ├── hooks.json ← Codex hook registration
249
+ │ └── .mcp.json ← odd-flow MCP config for Codex
222
250
  └── docs/
223
251
  ├── plan.md ← Master Implementation Plan
224
252
  ├── contract-map.md ← Contracts and dependency graph
@@ -229,13 +257,13 @@ my-project/
229
257
  └── ui/ ← UI specifications per outcome
230
258
  ```
231
259
 
232
- Only the config directories for your target agent are generated (`.claude/`, `.opencode/`, or both).
260
+ Only the config directories for your target agent are generated (`.claude/`, `.opencode/`, `plugins/odd-studio/`, or a supported combination).
233
261
 
234
262
  ---
235
263
 
236
264
  ## Slash commands
237
265
 
238
- These are the top-level commands you can invoke in either Claude Code or OpenCode:
266
+ These are the top-level commands you can invoke in Claude Code, OpenCode, or Codex:
239
267
 
240
268
  | Command | What it does |
241
269
  |---------|-------------|
@@ -273,10 +301,13 @@ Once inside `/odd`, you can also use these sub-commands:
273
301
  ```bash
274
302
  npx odd-studio init [name] # Scaffold a new ODD project
275
303
  npx odd-studio init --agent opencode # Scaffold for OpenCode (local models)
304
+ npx odd-studio init --agent codex # Scaffold for Codex
276
305
  npx odd-studio init --agent both # Scaffold for both agents
306
+ npx odd-studio init --agent all # Scaffold for Claude Code, OpenCode, and Codex
277
307
  npx odd-studio init --skip-git # Scaffold without git init or the initial commit
278
308
  npx odd-studio status # Show current planning state (run from project dir)
279
309
  npx odd-studio upgrade # Update skills, hooks, and odd-flow config (run from project dir)
310
+ npx odd-studio uninstall # Remove ODD Studio agent assets from the project
280
311
  npx odd-studio export # Instructions for exporting the Session Brief
281
312
  ```
282
313
 
@@ -322,6 +353,7 @@ ODD Studio implements the methodology from **The ODD Way to Build Software with
322
353
  - One of:
323
354
  - **Claude Code** installed (`npm install -g @anthropic-ai/claude-code`)
324
355
  - **OpenCode** installed (`npm install -g opencode` or `brew install opencode`)
356
+ - **Codex** installed
325
357
  - odd-flow MCP is configured automatically by `npx odd-studio init`
326
358
 
327
359
  ### For local model builds (OpenCode)
@@ -10,7 +10,7 @@ export function registerInit(program, deps) {
10
10
  program
11
11
  .command('init [project-name]')
12
12
  .description('Scaffold a new ODD project and install /odd into your AI coding agent')
13
- .option('--agent <type>', 'Target agent: claude-code, opencode, both, or auto (default: auto)', 'auto')
13
+ .option('--agent <type>', 'Target agent: claude-code, opencode, codex, both, all, or auto (default: auto)', 'auto')
14
14
  .option('--skip-skill', 'Skip installing the /odd skills/commands globally (advanced)')
15
15
  .option('--skip-hooks', 'Skip installing safety hooks/plugin (not recommended)')
16
16
  .option('--skip-mcp', 'Skip configuring the odd-flow MCP server')
@@ -23,11 +23,12 @@ export function registerInit(program, deps) {
23
23
  const { default: scaffoldProject } = await import('../../scripts/scaffold-project.js');
24
24
 
25
25
  const agent = detectAgent(options.agent);
26
- const isClaude = agent === 'claude-code' || agent === 'both';
27
- const isOpenCode = agent === 'opencode' || agent === 'both';
26
+ const isClaude = agent === 'claude-code' || agent === 'both' || agent === 'all';
27
+ const isOpenCode = agent === 'opencode' || agent === 'both' || agent === 'all';
28
+ const isCodex = agent === 'codex' || agent === 'all';
28
29
  const targetDir = projectName ? path.resolve(process.cwd(), projectName) : process.cwd();
29
30
  const resolvedName = projectName || path.basename(process.cwd());
30
- const agentLabel = agent === 'both' ? 'Claude Code + OpenCode' : agent === 'opencode' ? 'OpenCode' : 'Claude Code';
31
+ const agentLabel = getAgentLabel(agent);
31
32
  const totalSteps = 5;
32
33
 
33
34
  console.log(chalk.bold(` Setting up: ${chalk.cyan(resolvedName)}`) + chalk.dim(` (${agentLabel})\n`));
@@ -45,13 +46,13 @@ export function registerInit(program, deps) {
45
46
  }
46
47
 
47
48
  await installSkills({ PACKAGE_ROOT, isClaude, isOpenCode, options, print, targetDir, totalSteps });
48
- await installHooks({ PACKAGE_ROOT, isClaude, isOpenCode, options, print, targetDir, totalSteps });
49
+ await installHooks({ PACKAGE_ROOT, isClaude, isOpenCode, isCodex, options, print, targetDir, totalSteps });
49
50
  await maybeInstallCheckpoint({ options, print, targetDir, totalSteps });
50
51
  await configureMcp({ agent, options, print, targetDir, totalSteps });
51
52
 
52
53
  print.blank();
53
54
  console.log(chalk.bold.green(' ✓ ODD Studio is ready.\n'));
54
- printNextSteps({ isClaude, isOpenCode, projectName });
55
+ printNextSteps({ isClaude, isOpenCode, isCodex, projectName });
55
56
  console.log(chalk.dim(' ODD Studio implements Outcome-Driven Development.'));
56
57
  console.log(chalk.dim(" At every step, you'll understand why — not just what to do."));
57
58
  print.blank();
@@ -98,7 +99,7 @@ async function installSkills(context) {
98
99
  }
99
100
 
100
101
  async function installHooks(context) {
101
- const { PACKAGE_ROOT, isClaude, isOpenCode, options, print, targetDir, totalSteps } = context;
102
+ const { PACKAGE_ROOT, isClaude, isOpenCode, isCodex, options, print, targetDir, totalSteps } = context;
102
103
 
103
104
  if (options.skipHooks) {
104
105
  print.step(3, totalSteps, 'Skipping hooks/plugin (--skip-hooks)');
@@ -109,6 +110,7 @@ async function installHooks(context) {
109
110
  const targets = [];
110
111
  if (isClaude) targets.push('hooks');
111
112
  if (isOpenCode) targets.push('plugin');
113
+ if (isCodex) targets.push('Codex plugin');
112
114
  print.step(3, totalSteps, `Installing safety ${targets.join(' + ')}...`);
113
115
  const spinner = ora({ text: '', indent: 4 }).start();
114
116
 
@@ -126,6 +128,12 @@ async function installHooks(context) {
126
128
  print.ok('OpenCode plugin installed → odd-studio-plugin.js');
127
129
  }));
128
130
  }
131
+ if (isCodex) {
132
+ const { default: setupCodexPlugin } = await import('../../scripts/setup-codex-plugin.js');
133
+ installs.push(setupCodexPlugin(PACKAGE_ROOT, targetDir).then(() => {
134
+ print.ok('Codex plugin installed → ' + chalk.cyan('plugins/odd-studio/'));
135
+ }));
136
+ }
129
137
  await Promise.all(installs);
130
138
  spinner.stop();
131
139
  } catch (error) {
@@ -184,7 +192,7 @@ async function configureMcp({ agent, options, print, targetDir, totalSteps }) {
184
192
  const { default: setupMcp } = await import('../../scripts/setup-mcp.js');
185
193
  const result = await setupMcp(agent, targetDir);
186
194
  spinner.stop();
187
- if (result.mcpJsonUpdated || result.openCodeUpdated) {
195
+ if (result.mcpJsonUpdated || result.openCodeUpdated || result.codexUpdated) {
188
196
  print.ok('odd-flow MCP server configured → ' + chalk.cyan('.mcp.json'));
189
197
  } else {
190
198
  print.ok('odd-flow MCP server already configured');
@@ -195,7 +203,7 @@ async function configureMcp({ agent, options, print, targetDir, totalSteps }) {
195
203
  }
196
204
  }
197
205
 
198
- function printNextSteps({ isClaude, isOpenCode, projectName }) {
206
+ function printNextSteps({ isClaude, isOpenCode, isCodex, projectName }) {
199
207
  console.log(chalk.bold(' Next steps:'));
200
208
  console.log(chalk.dim(' ─────────────────────────────────────────'));
201
209
  let stepN = 1;
@@ -208,6 +216,18 @@ function printNextSteps({ isClaude, isOpenCode, projectName }) {
208
216
  console.log(` ${stepN++}. Restart OpenCode: ` + chalk.cyan('quit and reopen') + chalk.dim(' (activates odd-flow memory + plugin)'));
209
217
  console.log(` ${stepN++}. Open your project: ` + chalk.cyan('opencode'));
210
218
  }
219
+ if (isCodex) {
220
+ console.log(` ${stepN++}. Restart Codex: ` + chalk.cyan('quit and reopen') + chalk.dim(' (activates the local ODD Studio plugin)'));
221
+ console.log(` ${stepN++}. Open your project in Codex`);
222
+ }
211
223
  console.log(` ${stepN++}. Start your ODD session: ` + chalk.cyan('/odd'));
212
224
  console.log();
213
225
  }
226
+
227
+ function getAgentLabel(agent) {
228
+ if (agent === 'all') return 'Claude Code + OpenCode + Codex';
229
+ if (agent === 'both') return 'Claude Code + OpenCode';
230
+ if (agent === 'opencode') return 'OpenCode';
231
+ if (agent === 'codex') return 'Codex';
232
+ return 'Claude Code';
233
+ }
@@ -2,7 +2,7 @@
2
2
 
3
3
  import path from 'path';
4
4
  import fs from 'fs-extra';
5
- import { OPEN_CODE_COMMAND_FILES } from '../../scripts/assets.js';
5
+ import { CODEX_PLUGIN_NAME, OPEN_CODE_COMMAND_FILES } from '../../scripts/assets.js';
6
6
  import { removeIfEmpty } from '../shared.js';
7
7
 
8
8
  export function registerUninstall(program, deps) {
@@ -11,19 +11,21 @@ export function registerUninstall(program, deps) {
11
11
  program
12
12
  .command('uninstall')
13
13
  .description('Remove all ODD Studio hooks, skills, commands, plugin, and MCP config from this project')
14
- .option('--agent <type>', 'Target agent: claude-code, opencode, both, or auto (default: auto)', 'auto')
14
+ .option('--agent <type>', 'Target agent: claude-code, opencode, codex, both, all, or auto (default: auto)', 'auto')
15
15
  .action(async (options) => {
16
16
  print.logo();
17
17
  const { default: detectAgent } = await import('../../scripts/detect-agent.js');
18
18
  const agent = detectAgent(options.agent);
19
- const isClaude = agent === 'claude-code' || agent === 'both';
20
- const isOpenCode = agent === 'opencode' || agent === 'both';
19
+ const isClaude = agent === 'claude-code' || agent === 'both' || agent === 'all';
20
+ const isOpenCode = agent === 'opencode' || agent === 'both' || agent === 'all';
21
+ const isCodex = agent === 'codex' || agent === 'all';
21
22
  const targetDir = process.cwd();
22
23
  const removals = [];
23
24
 
24
25
  console.log(' Removing ODD Studio from this project...\n');
25
26
  if (isClaude) await removeClaudeAssets(targetDir, removals);
26
27
  if (isOpenCode) await removeOpenCodeAssets(targetDir, removals);
28
+ if (isCodex) await removeCodexAssets(targetDir, removals);
27
29
  await removeMcpConfig(targetDir, removals);
28
30
 
29
31
  print.blank();
@@ -84,6 +86,22 @@ async function removeMcpConfig(targetDir, removals) {
84
86
  await removeMcpEntry(path.join(targetDir, 'opencode.json'), ['mcp', 'odd-flow'], 'odd-flow MCP server from opencode.json', removals);
85
87
  }
86
88
 
89
+ async function removeCodexAssets(targetDir, removals) {
90
+ await removeOwnedPath(path.join(targetDir, 'plugins', CODEX_PLUGIN_NAME), 'Codex ODD plugin', removals);
91
+ const marketplacePath = path.join(targetDir, '.agents', 'plugins', 'marketplace.json');
92
+ if (!fs.existsSync(marketplacePath)) return;
93
+
94
+ try {
95
+ const marketplace = await fs.readJson(marketplacePath);
96
+ if (!Array.isArray(marketplace.plugins)) return;
97
+ const nextPlugins = marketplace.plugins.filter((plugin) => plugin.name !== CODEX_PLUGIN_NAME);
98
+ if (nextPlugins.length === marketplace.plugins.length) return;
99
+ marketplace.plugins = nextPlugins;
100
+ await fs.writeJson(marketplacePath, marketplace, { spaces: 2 });
101
+ removals.push('Codex marketplace registration');
102
+ } catch {}
103
+ }
104
+
87
105
  async function removeMcpEntry(filePath, keyPath, label, removals) {
88
106
  if (!fs.existsSync(filePath)) return;
89
107
  try {
@@ -11,13 +11,14 @@ export function registerUpgrade(program, deps) {
11
11
  program
12
12
  .command('upgrade')
13
13
  .description('Upgrade the /odd skills/commands, hooks/plugin, and odd-flow config to the latest version')
14
- .option('--agent <type>', 'Target agent: claude-code, opencode, both, or auto (default: auto)', 'auto')
14
+ .option('--agent <type>', 'Target agent: claude-code, opencode, codex, both, all, or auto (default: auto)', 'auto')
15
15
  .action(async (options) => {
16
16
  print.logo();
17
17
  const { default: detectAgent } = await import('../../scripts/detect-agent.js');
18
18
  const agent = detectAgent(options.agent);
19
- const isClaude = agent === 'claude-code' || agent === 'both';
20
- const isOpenCode = agent === 'opencode' || agent === 'both';
19
+ const isClaude = agent === 'claude-code' || agent === 'both' || agent === 'all';
20
+ const isOpenCode = agent === 'opencode' || agent === 'both' || agent === 'all';
21
+ const isCodex = agent === 'codex' || agent === 'all';
21
22
  const targetDir = process.cwd();
22
23
  const stateFile = path.resolve(targetDir, '.odd', 'state.json');
23
24
 
@@ -30,6 +31,7 @@ export function registerUpgrade(program, deps) {
30
31
  console.log(chalk.bold(' Upgrading ODD Studio...\n'));
31
32
  if (isClaude) await upgradeClaude(PACKAGE_ROOT, targetDir);
32
33
  if (isOpenCode) await upgradeOpenCode(PACKAGE_ROOT, targetDir);
34
+ if (isCodex) await upgradeCodex(PACKAGE_ROOT, targetDir);
33
35
  await upgradeMcp(agent, targetDir);
34
36
  print.blank();
35
37
  print.ok('Upgrade complete.');
@@ -66,6 +68,13 @@ async function upgradeOpenCode(packageRoot, targetDir) {
66
68
  });
67
69
  }
68
70
 
71
+ async function upgradeCodex(packageRoot, targetDir) {
72
+ await runSpinner('Updating Codex plugin...', 'Codex plugin updated', async () => {
73
+ const { default: setupCodexPlugin } = await import('../../scripts/setup-codex-plugin.js');
74
+ await setupCodexPlugin(packageRoot, targetDir);
75
+ });
76
+ }
77
+
69
78
  async function upgradeMcp(agent, targetDir) {
70
79
  await runSpinner('Checking odd-flow MCP configuration...', null, async (spinner) => {
71
80
  const { default: setupMcp } = await import('../../scripts/setup-mcp.js');
package/bin/odd-studio.js CHANGED
@@ -15,7 +15,7 @@ import { registerUninstall } from './commands/uninstall.js';
15
15
  const __filename = fileURLToPath(import.meta.url);
16
16
  const __dirname = path.dirname(__filename);
17
17
  const require = createRequire(import.meta.url);
18
- const pkg = require('../package.json'); // v3.2.3
18
+ const pkg = require('../package.json'); // v3.3.1
19
19
 
20
20
  const PACKAGE_ROOT = path.resolve(__dirname, '..');
21
21
  const deps = { PACKAGE_ROOT, print };
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "odd-studio",
3
+ "version": "3.3.1",
4
+ "description": "Outcome-Driven Development planning and build harness for domain experts building serious software with AI.",
5
+ "author": {
6
+ "name": "ODD Studio"
7
+ },
8
+ "homepage": "https://github.com/odd-studio/odd-studio",
9
+ "repository": "https://github.com/odd-studio/odd-studio",
10
+ "license": "MIT",
11
+ "keywords": [
12
+ "odd-studio",
13
+ "outcome-driven-development",
14
+ "codex",
15
+ "planning",
16
+ "build"
17
+ ],
18
+ "skills": "./skills/",
19
+ "hooks": "./hooks.json",
20
+ "mcpServers": "./.mcp.json",
21
+ "interface": {
22
+ "displayName": "ODD Studio",
23
+ "shortDescription": "Guide planning, build sequencing, and verification with Outcome-Driven Development",
24
+ "longDescription": "Use ODD Studio to plan around personas, outcomes, contracts, and phased delivery, then run a disciplined build and verification flow with odd-flow memory and safety gates.",
25
+ "developerName": "ODD Studio",
26
+ "category": "Coding",
27
+ "capabilities": [
28
+ "Interactive",
29
+ "Read",
30
+ "Write"
31
+ ],
32
+ "websiteURL": "https://github.com/odd-studio/odd-studio",
33
+ "privacyPolicyURL": "https://github.com/odd-studio/odd-studio",
34
+ "termsOfServiceURL": "https://github.com/odd-studio/odd-studio",
35
+ "defaultPrompt": "Start or resume an ODD Studio planning and build session for this project",
36
+ "brandColor": "#111111",
37
+ "screenshots": []
38
+ }
39
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "mcpServers": {
3
+ "odd-flow": {
4
+ "type": "stdio",
5
+ "command": "npx",
6
+ "args": [
7
+ "-y",
8
+ "odd-flow@latest",
9
+ "mcp",
10
+ "start"
11
+ ],
12
+ "env": {}
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,3 @@
1
+ # ODD Studio for Codex
2
+
3
+ This plugin installs the ODD Studio planning and build harness into Codex as a project-local plugin.
@@ -0,0 +1,110 @@
1
+ {
2
+ "hooks": {
3
+ "PreToolUse": [
4
+ {
5
+ "matcher": "Agent",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "./hooks/odd-studio.sh brief-gate"
10
+ },
11
+ {
12
+ "type": "command",
13
+ "command": "./hooks/odd-studio.sh build-gate"
14
+ }
15
+ ]
16
+ },
17
+ {
18
+ "matcher": "Write|Edit",
19
+ "hooks": [
20
+ {
21
+ "type": "command",
22
+ "command": "./hooks/odd-studio.sh swarm-write"
23
+ },
24
+ {
25
+ "type": "command",
26
+ "command": "./hooks/odd-studio.sh verify-gate"
27
+ },
28
+ {
29
+ "type": "command",
30
+ "command": "./hooks/odd-studio.sh confirm-gate"
31
+ }
32
+ ]
33
+ },
34
+ {
35
+ "matcher": "Bash",
36
+ "hooks": [
37
+ {
38
+ "type": "command",
39
+ "command": "./hooks/odd-studio.sh commit-gate"
40
+ }
41
+ ]
42
+ }
43
+ ],
44
+ "UserPromptSubmit": [
45
+ {
46
+ "hooks": [
47
+ {
48
+ "type": "command",
49
+ "command": "./hooks/odd-studio.sh swarm-guard"
50
+ }
51
+ ]
52
+ }
53
+ ],
54
+ "PostToolUse": [
55
+ {
56
+ "matcher": "Bash",
57
+ "hooks": [
58
+ {
59
+ "type": "command",
60
+ "command": "./hooks/odd-studio.sh session-save"
61
+ }
62
+ ]
63
+ },
64
+ {
65
+ "matcher": "mcp__odd-flow__memory_store",
66
+ "hooks": [
67
+ {
68
+ "type": "command",
69
+ "command": "./hooks/odd-studio.sh store-validate"
70
+ }
71
+ ]
72
+ },
73
+ {
74
+ "matcher": "mcp__odd-flow__coordination_sync",
75
+ "hooks": [
76
+ {
77
+ "type": "command",
78
+ "command": "./hooks/odd-studio.sh sync-validate"
79
+ }
80
+ ]
81
+ },
82
+ {
83
+ "matcher": "Write",
84
+ "hooks": [
85
+ {
86
+ "type": "command",
87
+ "command": "./hooks/odd-studio.sh code-quality"
88
+ },
89
+ {
90
+ "type": "command",
91
+ "command": "./hooks/odd-studio.sh brief-quality"
92
+ },
93
+ {
94
+ "type": "command",
95
+ "command": "./hooks/odd-studio.sh outcome-quality"
96
+ }
97
+ ]
98
+ },
99
+ {
100
+ "matcher": "Edit",
101
+ "hooks": [
102
+ {
103
+ "type": "command",
104
+ "command": "./hooks/odd-studio.sh code-quality"
105
+ }
106
+ ]
107
+ }
108
+ ]
109
+ }
110
+ }
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "odd-studio",
3
- "version": "3.2.3",
4
- "description": "Outcome-Driven Development for AI coding agents — a planning and build harness for domain experts building serious software with AI. Works with Claude Code and OpenCode.",
3
+ "version": "3.3.1",
4
+ "description": "Outcome-Driven Development for AI coding agents — a planning and build harness for domain experts building serious software with AI. Works with Claude Code, OpenCode, and Codex.",
5
5
  "keywords": [
6
6
  "claude-code",
7
7
  "opencode",
8
+ "codex",
8
9
  "odd",
9
10
  "outcome-driven-development",
10
11
  "ai-development",
@@ -35,6 +36,7 @@
35
36
  "skill/",
36
37
  "hooks/",
37
38
  "plugins/",
39
+ "codex-plugin/",
38
40
  "templates/",
39
41
  ".claude-plugin/"
40
42
  ],
package/scripts/assets.js CHANGED
@@ -1,6 +1,14 @@
1
1
  'use strict';
2
2
 
3
3
  export const ODD_HOOK_FILE = 'odd-studio.sh';
4
+ export const CODEX_PLUGIN_NAME = 'odd-studio';
5
+ export const CODEX_MARKETPLACE_ROOT = {
6
+ name: 'local-plugins',
7
+ interface: {
8
+ displayName: 'Local plugins',
9
+ },
10
+ plugins: [],
11
+ };
4
12
 
5
13
  export const OPEN_CODE_COMMAND_FILES = [
6
14
  'odd.md',
@@ -5,20 +5,28 @@ import os from 'os';
5
5
 
6
6
  const CLAUDE_DIR = path.join(os.homedir(), '.claude');
7
7
  const OC_DIR = path.join(os.homedir(), '.config', 'opencode');
8
+ const CODEX_DIR = path.join(os.homedir(), '.codex');
8
9
 
9
10
  /**
10
11
  * Detects which AI coding agent is installed on this machine.
11
12
  *
12
- * @param {string} override - 'claude-code', 'opencode', 'both', or 'auto'
13
- * @returns {'claude-code' | 'opencode' | 'both'}
13
+ * @param {string} override - 'claude-code', 'opencode', 'codex', 'both', 'all', or 'auto'
14
+ * @param {{ existsSync?: (path: string) => boolean }} options
15
+ * @returns {'claude-code' | 'opencode' | 'codex' | 'both' | 'all'}
14
16
  */
15
- export default function detectAgent(override = 'auto') {
17
+ export default function detectAgent(override = 'auto', options = {}) {
16
18
  if (override !== 'auto') return override;
19
+ const exists = options.existsSync || fs.existsSync;
17
20
 
18
- const hasClaude = fs.existsSync(CLAUDE_DIR);
19
- const hasOpenCode = fs.existsSync(OC_DIR);
21
+ const hasClaude = exists(CLAUDE_DIR);
22
+ const hasOpenCode = exists(OC_DIR);
23
+ const hasCodex = exists(CODEX_DIR);
20
24
 
25
+ if (hasClaude && hasOpenCode && hasCodex) return 'all';
21
26
  if (hasClaude && hasOpenCode) return 'both';
27
+ if (hasClaude && hasCodex) return 'all';
28
+ if (hasOpenCode && hasCodex) return 'all';
29
+ if (hasCodex) return 'codex';
22
30
  if (hasOpenCode) return 'opencode';
23
31
  return 'claude-code';
24
32
  }
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import { COMMANDS } from './command-definitions.js';
5
+ import { CODEX_PLUGIN_NAME } from './assets.js';
6
+
7
+ const CODEX_ODD_ROOT = `plugins/${CODEX_PLUGIN_NAME}/skills/odd/`;
8
+
9
+ export default async function installCodexCommands(pluginDest) {
10
+ const commandsDest = path.join(pluginDest, 'commands');
11
+ await fs.ensureDir(commandsDest);
12
+
13
+ for (const cmd of COMMANDS) {
14
+ const content = cmd.source
15
+ ? buildMainCommand(cmd)
16
+ : buildSubcommand(cmd);
17
+ await fs.writeFile(path.join(commandsDest, `${cmd.name}.md`), content);
18
+ }
19
+
20
+ return { destination: commandsDest, commandCount: COMMANDS.length };
21
+ }
22
+
23
+ function buildMainCommand(cmd) {
24
+ return [
25
+ '---',
26
+ `description: "${cmd.description}"`,
27
+ '---',
28
+ '',
29
+ '# /odd',
30
+ '',
31
+ 'You are now operating as the ODD Studio coach.',
32
+ '',
33
+ 'Read this file now:',
34
+ `- \`${CODEX_ODD_ROOT}SKILL.md\` — the full ODD Studio coach`,
35
+ '',
36
+ 'Then execute the startup state check exactly as documented.',
37
+ '',
38
+ ].join('\n');
39
+ }
40
+
41
+ function buildSubcommand(cmd) {
42
+ return [
43
+ '---',
44
+ `description: "${cmd.description}"`,
45
+ '---',
46
+ '',
47
+ `# /${cmd.name}`,
48
+ '',
49
+ rewriteBody(cmd.body),
50
+ '',
51
+ ].join('\n');
52
+ }
53
+
54
+ function rewriteBody(body) {
55
+ return body.replace(/`\.opencode\/odd\//g, `\`${CODEX_ODD_ROOT}`);
56
+ }
@@ -16,6 +16,9 @@ export default async function scaffoldProject(targetDir, projectName, agent = 'c
16
16
  }
17
17
  const isClaude = agent === 'claude-code' || agent === 'both';
18
18
  const isOpenCode = agent === 'opencode' || agent === 'both';
19
+ const isCodex = agent === 'codex' || agent === 'all';
20
+ const wantsAgentsMd = isOpenCode || isCodex;
21
+ const wantsClaudeMd = isClaude || agent === 'all';
19
22
 
20
23
  await fs.ensureDir(targetDir);
21
24
 
@@ -31,9 +34,8 @@ export default async function scaffoldProject(targetDir, projectName, agent = 'c
31
34
  return !fs.existsSync(destDir) || fs.readdirSync(destDir).length === 0;
32
35
  }
33
36
  // Skip CLAUDE.md if OpenCode-only
34
- if (basename === 'CLAUDE.md' && !isClaude) return false;
35
- // Skip AGENTS.md if Claude-only
36
- if (basename === 'AGENTS.md' && !isOpenCode) return false;
37
+ if (basename === 'CLAUDE.md' && !wantsClaudeMd) return false;
38
+ if (basename === 'AGENTS.md' && !wantsAgentsMd) return false;
37
39
  return true;
38
40
  },
39
41
  });
@@ -0,0 +1,117 @@
1
+ 'use strict';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import {
5
+ CODEX_MARKETPLACE_ROOT,
6
+ CODEX_PLUGIN_NAME,
7
+ ODD_HOOK_FILE,
8
+ } from './assets.js';
9
+ import installCodexCommands from './install-codex-commands.js';
10
+
11
+ const PLUGIN_RELATIVE_PATH = `./plugins/${CODEX_PLUGIN_NAME}`;
12
+ const MARKETPLACE_ENTRY = {
13
+ name: CODEX_PLUGIN_NAME,
14
+ source: {
15
+ source: 'local',
16
+ path: PLUGIN_RELATIVE_PATH,
17
+ },
18
+ policy: {
19
+ installation: 'AVAILABLE',
20
+ authentication: 'ON_INSTALL',
21
+ },
22
+ category: 'Coding',
23
+ };
24
+
25
+ export default async function setupCodexPlugin(packageRoot, targetDir) {
26
+ const pluginSource = path.join(packageRoot, 'codex-plugin');
27
+ const pluginDest = path.join(targetDir, 'plugins', CODEX_PLUGIN_NAME);
28
+ const skillSource = path.join(packageRoot, 'skill');
29
+ const hookSource = path.join(packageRoot, 'hooks', ODD_HOOK_FILE);
30
+
31
+ if (!fs.existsSync(pluginSource)) {
32
+ throw new Error(`Codex plugin source not found: ${pluginSource}`);
33
+ }
34
+
35
+ await fs.copy(pluginSource, pluginDest, { overwrite: true });
36
+ await installCodexSkills(skillSource, pluginDest);
37
+ await installCodexCommands(pluginDest);
38
+ await installCodexHook(hookSource, pluginDest);
39
+ const marketplaceUpdated = await updateMarketplace(targetDir);
40
+
41
+ return { destination: pluginDest, marketplaceUpdated };
42
+ }
43
+
44
+ async function installCodexSkills(skillSource, pluginDest) {
45
+ const skillsDest = path.join(pluginDest, 'skills');
46
+ await fs.ensureDir(skillsDest);
47
+
48
+ const oddDest = path.join(skillsDest, 'odd');
49
+ await fs.ensureDir(oddDest);
50
+
51
+ const entries = await fs.readdir(skillSource, { withFileTypes: true });
52
+ for (const entry of entries) {
53
+ const src = path.join(skillSource, entry.name);
54
+ if (entry.isDirectory() && fs.existsSync(path.join(src, 'SKILL.md'))) {
55
+ await fs.copy(src, path.join(skillsDest, entry.name), { overwrite: true });
56
+ await rewriteSkillPaths(path.join(skillsDest, entry.name, 'SKILL.md'));
57
+ continue;
58
+ }
59
+
60
+ await fs.copy(src, path.join(oddDest, entry.name), { overwrite: true });
61
+ }
62
+
63
+ await rewriteSkillPaths(path.join(oddDest, 'SKILL.md'));
64
+ }
65
+
66
+ async function rewriteSkillPaths(filePath) {
67
+ if (!fs.existsSync(filePath)) return;
68
+ let content = await fs.readFile(filePath, 'utf8');
69
+ content = content.replace(/Works with Claude Code and OpenCode\./g, 'Works with Claude Code, OpenCode, and Codex.');
70
+ content = content.replace(/`\.claude\/skills\/odd\//g, '`plugins/odd-studio/skills/odd/');
71
+ content = content.replace(/\.claude\/skills\/odd\//g, 'plugins/odd-studio/skills/odd/');
72
+ await fs.writeFile(filePath, content);
73
+ }
74
+
75
+ async function installCodexHook(hookSource, pluginDest) {
76
+ const hooksDest = path.join(pluginDest, 'hooks');
77
+ await fs.ensureDir(hooksDest);
78
+ const hookDest = path.join(hooksDest, ODD_HOOK_FILE);
79
+ await fs.copy(hookSource, hookDest, { overwrite: true });
80
+ await fs.chmod(hookDest, 0o755);
81
+ }
82
+
83
+ async function updateMarketplace(targetDir) {
84
+ const marketplacePath = path.join(targetDir, '.agents', 'plugins', 'marketplace.json');
85
+ await fs.ensureDir(path.dirname(marketplacePath));
86
+
87
+ let marketplace = CODEX_MARKETPLACE_ROOT;
88
+ if (fs.existsSync(marketplacePath)) {
89
+ try {
90
+ marketplace = await fs.readJson(marketplacePath);
91
+ } catch {
92
+ marketplace = CODEX_MARKETPLACE_ROOT;
93
+ }
94
+ }
95
+
96
+ if (!marketplace.name) marketplace.name = CODEX_MARKETPLACE_ROOT.name;
97
+ if (!marketplace.interface) marketplace.interface = CODEX_MARKETPLACE_ROOT.interface;
98
+ if (!marketplace.interface.displayName) {
99
+ marketplace.interface.displayName = CODEX_MARKETPLACE_ROOT.interface.displayName;
100
+ }
101
+ if (!Array.isArray(marketplace.plugins)) marketplace.plugins = [];
102
+
103
+ const existingIndex = marketplace.plugins.findIndex((plugin) => plugin.name === CODEX_PLUGIN_NAME);
104
+ if (existingIndex === -1) {
105
+ marketplace.plugins.push(MARKETPLACE_ENTRY);
106
+ await fs.writeJson(marketplacePath, marketplace, { spaces: 2 });
107
+ return true;
108
+ }
109
+
110
+ const existing = marketplace.plugins[existingIndex];
111
+ const changed = JSON.stringify(existing) !== JSON.stringify(MARKETPLACE_ENTRY);
112
+ if (changed) {
113
+ marketplace.plugins[existingIndex] = MARKETPLACE_ENTRY;
114
+ await fs.writeJson(marketplacePath, marketplace, { spaces: 2 });
115
+ }
116
+ return changed;
117
+ }
@@ -24,14 +24,14 @@ const ODD_FLOW_SERVER_ENTRY = {
24
24
  *
25
25
  * Nothing is written to ~/. Both paths are idempotent.
26
26
  *
27
- * @param {'claude-code' | 'opencode' | 'both'} agent - Target agent
27
+ * @param {'claude-code' | 'opencode' | 'codex' | 'both' | 'all'} agent - Target agent
28
28
  * @param {string} targetDir - Project directory
29
29
  */
30
30
  export default async function setupMcp(agent = 'claude-code', targetDir) {
31
- const result = { mcpJsonUpdated: false, openCodeUpdated: false };
31
+ const result = { mcpJsonUpdated: false, openCodeUpdated: false, codexUpdated: false };
32
32
 
33
33
  // ── Claude Code: <projectDir>/.mcp.json ───────────────────────────────────
34
- if (agent === 'claude-code' || agent === 'both') {
34
+ if (agent === 'claude-code' || agent === 'both' || agent === 'all') {
35
35
  const mcpJsonPath = path.join(targetDir, '.mcp.json');
36
36
  let mcpConfig = { mcpServers: {} };
37
37
  if (fs.existsSync(mcpJsonPath)) {
@@ -55,7 +55,7 @@ export default async function setupMcp(agent = 'claude-code', targetDir) {
55
55
  }
56
56
 
57
57
  // ── OpenCode: <projectDir>/opencode.json ──────────────────────────────────
58
- if (agent === 'opencode' || agent === 'both') {
58
+ if (agent === 'opencode' || agent === 'both' || agent === 'all') {
59
59
  const ocConfigPath = path.join(targetDir, 'opencode.json');
60
60
 
61
61
  let ocConfig = {};
@@ -84,5 +84,28 @@ export default async function setupMcp(agent = 'claude-code', targetDir) {
84
84
  }
85
85
  }
86
86
 
87
+ if (agent === 'codex' || agent === 'all') {
88
+ const codexMcpPath = path.join(targetDir, 'plugins', 'odd-studio', '.mcp.json');
89
+ await fs.ensureDir(path.dirname(codexMcpPath));
90
+ let codexConfig = { mcpServers: {} };
91
+ if (fs.existsSync(codexMcpPath)) {
92
+ try {
93
+ codexConfig = await fs.readJson(codexMcpPath);
94
+ } catch {
95
+ codexConfig = { mcpServers: {} };
96
+ }
97
+ }
98
+ if (!codexConfig.mcpServers) codexConfig.mcpServers = {};
99
+
100
+ if (!codexConfig.mcpServers['odd-flow']) {
101
+ if (fs.existsSync(codexMcpPath)) {
102
+ await fs.copy(codexMcpPath, codexMcpPath + '.bak', { overwrite: true });
103
+ }
104
+ codexConfig.mcpServers['odd-flow'] = ODD_FLOW_SERVER_ENTRY;
105
+ await fs.writeJson(codexMcpPath, codexConfig, { spaces: 2 });
106
+ result.codexUpdated = true;
107
+ }
108
+ }
109
+
87
110
  return result;
88
111
  }
package/skill/SKILL.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: "odd"
3
- version: "3.2.3"
4
- description: "Outcome-Driven Development planning and build coach. Use /odd to start or resume an ODD project — building personas, writing outcomes, mapping contracts, creating a Master Implementation Plan, and directing a odd-flow-powered build. Designed for domain experts who are not developers. Works with Claude Code and OpenCode."
3
+ version: "3.3.1"
4
+ description: "Outcome-Driven Development planning and build coach. Use /odd to start or resume an ODD project — building personas, writing outcomes, mapping contracts, creating a Master Implementation Plan, and directing a odd-flow-powered build. Designed for domain experts who are not developers. Works with Claude Code, OpenCode, and Codex."
5
5
  ---
6
6
 
7
7
  # ODD Studio — Outcome-Driven Development Coach
@@ -35,7 +35,7 @@ Display this when no existing state is found:
35
35
 
36
36
  ---
37
37
 
38
- Welcome to ODD Studio v3.2.3.
38
+ Welcome to ODD Studio v3.3.1.
39
39
 
40
40
  You are about to plan and build something real — using a methodology called Outcome-Driven Development. Before we write a single line of code, we are going to get precise about three things:
41
41
 
@@ -59,7 +59,7 @@ Display this when existing state is found. Replace the bracketed values with act
59
59
 
60
60
  ---
61
61
 
62
- Welcome back to ODD Studio v3.2.3.
62
+ Welcome back to ODD Studio v3.3.1.
63
63
 
64
64
  **Project:** [project.name]
65
65
  **Current Phase:** [state.currentPhase]