cclaw-cli 0.39.1 → 0.40.0

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/README.md CHANGED
@@ -127,9 +127,12 @@ Plus harness-specific shims:
127
127
  - `.claude/commands/cc*.md` + `.claude/hooks/hooks.json`
128
128
  - `.cursor/commands/cc*.md` + `.cursor/hooks.json` + `.cursor/rules/cclaw-workflow.mdc`
129
129
  - `.opencode/commands/cc*.md` + `.opencode/plugins/cclaw-plugin.mjs`
130
- - `.agents/skills/cclaw-cc*/SKILL.md` (Codex; activated via `/use cclaw-cc`
131
- or description-based auto-matching — Codex no longer reads `.codex/commands/`
132
- or `.codex/hooks.json`, and `cclaw sync` cleans those up if present)
130
+ - `.agents/skills/cc*/SKILL.md` + `.codex/hooks.json` (Codex; skills are
131
+ activated via `/use cc` or description-based auto-matching. Hooks
132
+ require Codex CLI ≥ v0.114 and `[features] codex_hooks = true` in
133
+ `~/.codex/config.toml`; `cclaw init --codex` offers to patch that flag
134
+ for you. `.codex/commands/` and the legacy `.agents/skills/cclaw-cc*/`
135
+ folders are auto-cleaned on sync.)
133
136
  - `AGENTS.md` with a managed routing block (includes a Codex-specific note)
134
137
 
135
138
  `.cclaw/config.yaml` holds every tunable key (prompt guard strictness,
@@ -357,7 +360,7 @@ closes every real gap with a documented fallback — not a silent waiver.
357
360
  | Claude Code | full (named subagents) | `native` | full | `AskUserQuestion` | [`claude-playbook.md`](./src/content/harness-playbooks.ts) |
358
361
  | Cursor | generic Task dispatcher | `generic-dispatch` | full | `AskQuestion` | `cursor-playbook.md` |
359
362
  | OpenCode | plugin / in-session | `role-switch` | plugin | plain-text | `opencode-playbook.md` |
360
- | OpenAI Codex | in-session only | `role-switch` (evidenceRefs required) | none (no hooks API) | plain-text | `codex-playbook.md` |
363
+ | OpenAI Codex | in-session only | `role-switch` (evidenceRefs required) | limited (Bash-only `PreToolUse`/`PostToolUse`; requires `codex_hooks` feature flag) | plain-text | `codex-playbook.md` |
361
364
 
362
365
  What the fallbacks mean:
363
366
 
@@ -380,16 +383,21 @@ What the fallbacks mean:
380
383
  harness declares it. Currently unused — v0.33 removed the old
381
384
  Codex-only auto-waiver path.
382
385
 
383
- > **Codex note (v0.39+).** Codex CLI deprecated custom prompts and the
384
- > `.codex/hooks.json` API, so cclaw installs Codex entry points as
385
- > native **skills** under `.agents/skills/cclaw-cc*/SKILL.md`. Invoke
386
- > them with `/use cclaw-cc`, `/use cclaw-cc-next`, `/use cclaw-cc-view`,
387
- > `/use cclaw-cc-ops`, `/use cclaw-cc-ideate`, or just say something
388
- > like *"run cc for payments refund fix"* — Codex auto-matches skills
389
- > from their description. Hook-driven checks (prompt-guard, stop-save,
390
- > post-tool context monitor) are substituted in the `cclaw-cc*` skill
391
- > bodies as explicit agent steps; run `cclaw doctor` to see what's
392
- > missing and how the playbook compensates.
386
+ > **Codex note (v0.40+).** Codex CLI deprecated custom prompts in v0.89
387
+ > (Jan 2026), but Codex v0.114 (Mar 2026) grew an experimental
388
+ > lifecycle hooks API. cclaw installs Codex entry points as native
389
+ > **skills** under `.agents/skills/cc*/SKILL.md` (invoke with `/use cc`,
390
+ > `/use cc-next`, `/use cc-view`, `/use cc-ops`, `/use cc-ideate`, or
391
+ > by typing `/cc …` in plain text — Codex auto-matches from the skill
392
+ > description) **and** writes `.codex/hooks.json` so session-start
393
+ > rehydration, stop-checkpoint, prompt-guard, workflow-guard, and
394
+ > context-monitor fire automatically as long as you enable the
395
+ > `codex_hooks` feature flag in `~/.codex/config.toml`. `cclaw init
396
+ > --codex` asks for consent before patching that file. Codex's
397
+ > `PreToolUse`/`PostToolUse` are Bash-only; the stage skills compensate
398
+ > for `Write`/`Edit`/`MCP` tool calls with explicit in-turn checks. Run
399
+ > `cclaw doctor` to see the current state of hooks, the feature flag,
400
+ > and any legacy layout to clean up.
393
401
 
394
402
  The full capability matrix lives in
395
403
  [`docs/harnesses.md`](./docs/harnesses.md). Per-harness playbooks are
package/dist/cli.js CHANGED
@@ -15,6 +15,7 @@ import { CCLAW_VERSION, RUNTIME_ROOT } from "./constants.js";
15
15
  import { createDefaultConfig } from "./config.js";
16
16
  import { detectHarnesses } from "./init-detect.js";
17
17
  import { HARNESS_ADAPTERS } from "./harness-adapters.js";
18
+ import { classifyCodexHooksFlag, codexConfigPath, patchCodexHooksFlag, readCodexConfig, writeCodexConfig } from "./codex-feature-flag.js";
18
19
  import { runEval } from "./eval/runner.js";
19
20
  import { createStderrProgressLogger } from "./eval/progress.js";
20
21
  import { writeBaselinesFromReport } from "./eval/baseline.js";
@@ -152,7 +153,7 @@ function buildInitSurfacePreview(harnesses) {
152
153
  for (const harness of harnesses) {
153
154
  const adapter = HARNESS_ADAPTERS[harness];
154
155
  if (adapter.shimKind === "skill") {
155
- lines.push(`${adapter.commandDir}/cclaw-cc*/SKILL.md`);
156
+ lines.push(`${adapter.commandDir}/cc*/SKILL.md`);
156
157
  }
157
158
  else {
158
159
  lines.push(`${adapter.commandDir}/cc*.md`);
@@ -164,9 +165,12 @@ function buildInitSurfacePreview(harnesses) {
164
165
  lines.push(".cursor/hooks.json");
165
166
  lines.push(".cursor/rules/cclaw-workflow.mdc");
166
167
  }
167
- // Codex has no hooks file — it reads skills from `.agents/skills/` only
168
- // (v0.39.0+). Legacy `.codex/commands/*` and `.codex/hooks.json` are
169
- // auto-cleaned on sync.
168
+ if (harness === "codex") {
169
+ // v0.40.0: .codex/hooks.json is managed again now that Codex CLI
170
+ // grew a real hooks API (v0.114+, behind the `codex_hooks`
171
+ // feature flag). Legacy `.codex/commands/*` is still auto-cleaned.
172
+ lines.push(".codex/hooks.json (requires `codex_hooks = true` in ~/.codex/config.toml)");
173
+ }
170
174
  if (harness === "opencode") {
171
175
  lines.push(".opencode/plugins/cclaw-plugin.mjs");
172
176
  lines.push("opencode.json(.c) plugin registration");
@@ -207,6 +211,85 @@ async function promptInitConfig(defaults, ctx) {
207
211
  rl.close();
208
212
  }
209
213
  }
214
+ /**
215
+ * When Codex is one of the installed harnesses, check the Codex CLI
216
+ * config file for the `codex_hooks` feature flag. If it is missing or
217
+ * disabled, offer to patch it in with the user's explicit consent.
218
+ *
219
+ * The function is deliberately advisory: it never fails init — the worst
220
+ * case is that Codex runs without the hooks engine, which is exactly
221
+ * how v0.39.x already shipped. We always print a resolution hint so
222
+ * the user knows what to do next regardless of which branch was taken.
223
+ */
224
+ async function maybeEnableCodexHooksFlag(harnesses, parsed, ctx) {
225
+ if (!harnesses || !harnesses.includes("codex"))
226
+ return;
227
+ const configPath = codexConfigPath();
228
+ let existing;
229
+ try {
230
+ existing = await readCodexConfig(configPath);
231
+ }
232
+ catch (err) {
233
+ ctx.stdout.write(`note: Could not read ${configPath} to check the codex_hooks flag: ` +
234
+ `${err instanceof Error ? err.message : String(err)}\n`);
235
+ return;
236
+ }
237
+ const state = classifyCodexHooksFlag(existing);
238
+ if (state === "enabled") {
239
+ return;
240
+ }
241
+ const humanState = state === "missing-file"
242
+ ? "Codex config file does not exist yet"
243
+ : state === "missing-section"
244
+ ? "no [features] section"
245
+ : state === "missing-key"
246
+ ? "no codex_hooks key"
247
+ : "codex_hooks is not enabled";
248
+ const instructions = `To enable Codex hooks manually later, ensure ${configPath} contains:\n` +
249
+ ` [features]\n codex_hooks = true\n`;
250
+ if (parsed.interactive === false) {
251
+ ctx.stdout.write(`note: codex_hooks feature flag is not enabled (${humanState}).\n` +
252
+ ` cclaw wrote .codex/hooks.json, but Codex will ignore it until you enable the flag.\n` +
253
+ ` ${instructions}`);
254
+ return;
255
+ }
256
+ if (!isInitPromptAllowed(ctx)) {
257
+ ctx.stdout.write(`note: codex_hooks feature flag is not enabled (${humanState}).\n` +
258
+ ` cclaw wrote .codex/hooks.json, but Codex will ignore it until you enable the flag.\n` +
259
+ ` ${instructions}`);
260
+ return;
261
+ }
262
+ const rl = createInterface({
263
+ input: process.stdin,
264
+ output: ctx.stdout
265
+ });
266
+ try {
267
+ const answer = (await rl.question(`\nCodex CLI hooks are off (${humanState}).\n` +
268
+ `Enable [features] codex_hooks = true in ${configPath} now? [y/N]: `)).trim().toLowerCase();
269
+ const yes = answer === "y" || answer === "yes";
270
+ if (!yes) {
271
+ ctx.stdout.write(`Leaving ${configPath} untouched. ${instructions}`);
272
+ return;
273
+ }
274
+ const { updated, changed } = patchCodexHooksFlag(existing);
275
+ if (!changed) {
276
+ ctx.stdout.write(`codex_hooks is already enabled — no changes written.\n`);
277
+ return;
278
+ }
279
+ try {
280
+ await writeCodexConfig(configPath, updated);
281
+ ctx.stdout.write(`Enabled [features] codex_hooks = true in ${configPath}.\n`);
282
+ }
283
+ catch (err) {
284
+ ctx.stdout.write(`Could not write ${configPath}: ` +
285
+ `${err instanceof Error ? err.message : String(err)}\n` +
286
+ `${instructions}`);
287
+ }
288
+ }
289
+ finally {
290
+ rl.close();
291
+ }
292
+ }
210
293
  async function resolveInitInputs(parsed, ctx) {
211
294
  const detectedHarnesses = parsed.harnesses ? [] : await detectHarnesses(ctx.cwd);
212
295
  const autoHarnesses = parsed.harnesses
@@ -744,6 +827,7 @@ async function runCommand(parsed, ctx) {
744
827
  }
745
828
  const trackNote = effectiveTrack ? ` (track=${effectiveTrack})` : "";
746
829
  info(ctx, `Initialized .cclaw runtime and generated harness shims${trackNote}`);
830
+ await maybeEnableCodexHooksFlag(effectiveHarnesses, parsed, ctx);
747
831
  return 0;
748
832
  }
749
833
  if (command === "sync") {
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Manage the `codex_hooks` feature flag in `~/.codex/config.toml`.
3
+ *
4
+ * Codex CLI ≥ v0.114 (Mar 2026) exposes lifecycle hooks via
5
+ * `.codex/hooks.json`, but the hooks engine is inert unless the user has
6
+ * opted into it with:
7
+ *
8
+ * ```toml
9
+ * [features]
10
+ * codex_hooks = true
11
+ * ```
12
+ *
13
+ * in `$CODEX_HOME/config.toml` (default: `~/.codex/config.toml`).
14
+ * cclaw's `init --codex` prompts the user to flip this flag for them;
15
+ * this module owns the detection / mutation code so the prompt logic in
16
+ * `cli.ts` stays small and testable.
17
+ *
18
+ * The TOML mutations here are intentionally surgical — we never reparse
19
+ * or rewrite the whole document. A deliberately narrow regex based
20
+ * approach lets the function stay dependency-free and preserves the
21
+ * user's comments, whitespace, and custom key ordering.
22
+ */
23
+ /**
24
+ * Absolute path of the Codex config file. Respects `$CODEX_HOME` when
25
+ * present (the only override Codex CLI documents); falls back to
26
+ * `~/.codex/config.toml` otherwise.
27
+ */
28
+ export declare function codexConfigPath(env?: NodeJS.ProcessEnv): string;
29
+ export type CodexHooksFlagState = "enabled" | "disabled" | "missing-key" | "missing-section" | "missing-file";
30
+ /**
31
+ * Inspect a TOML document and decide which of the five canonical states
32
+ * it represents. Comments and blank lines are ignored. Only the first
33
+ * `[features]` section is considered — duplicates are technically invalid
34
+ * TOML and Codex rejects them, so cclaw does not try to be clever there.
35
+ */
36
+ export declare function classifyCodexHooksFlag(toml: string | null): CodexHooksFlagState;
37
+ /**
38
+ * Return a TOML document with `[features] codex_hooks = true` set.
39
+ * Preserves all other content verbatim:
40
+ * - If the document lacks a `[features]` section, we append one at the
41
+ * end of the file (separated by a blank line).
42
+ * - If `[features]` exists without `codex_hooks`, we insert the key
43
+ * immediately after the header.
44
+ * - If `codex_hooks` exists with any non-`true` value, we rewrite
45
+ * just that line.
46
+ * - If the flag is already `true`, the input is returned unchanged.
47
+ */
48
+ export declare function patchCodexHooksFlag(toml: string | null): {
49
+ updated: string;
50
+ changed: boolean;
51
+ };
52
+ /**
53
+ * Read the Codex config, return `null` when the file does not exist.
54
+ * All other read errors propagate so callers can surface a useful
55
+ * message instead of silently degrading.
56
+ */
57
+ export declare function readCodexConfig(configPath: string): Promise<string | null>;
58
+ export declare function writeCodexConfig(configPath: string, content: string): Promise<void>;
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Manage the `codex_hooks` feature flag in `~/.codex/config.toml`.
3
+ *
4
+ * Codex CLI ≥ v0.114 (Mar 2026) exposes lifecycle hooks via
5
+ * `.codex/hooks.json`, but the hooks engine is inert unless the user has
6
+ * opted into it with:
7
+ *
8
+ * ```toml
9
+ * [features]
10
+ * codex_hooks = true
11
+ * ```
12
+ *
13
+ * in `$CODEX_HOME/config.toml` (default: `~/.codex/config.toml`).
14
+ * cclaw's `init --codex` prompts the user to flip this flag for them;
15
+ * this module owns the detection / mutation code so the prompt logic in
16
+ * `cli.ts` stays small and testable.
17
+ *
18
+ * The TOML mutations here are intentionally surgical — we never reparse
19
+ * or rewrite the whole document. A deliberately narrow regex based
20
+ * approach lets the function stay dependency-free and preserves the
21
+ * user's comments, whitespace, and custom key ordering.
22
+ */
23
+ import fs from "node:fs/promises";
24
+ import os from "node:os";
25
+ import path from "node:path";
26
+ /**
27
+ * Absolute path of the Codex config file. Respects `$CODEX_HOME` when
28
+ * present (the only override Codex CLI documents); falls back to
29
+ * `~/.codex/config.toml` otherwise.
30
+ */
31
+ export function codexConfigPath(env = process.env) {
32
+ const codexHome = env.CODEX_HOME && env.CODEX_HOME.trim().length > 0
33
+ ? env.CODEX_HOME
34
+ : path.join(os.homedir(), ".codex");
35
+ return path.join(codexHome, "config.toml");
36
+ }
37
+ /**
38
+ * Inspect a TOML document and decide which of the five canonical states
39
+ * it represents. Comments and blank lines are ignored. Only the first
40
+ * `[features]` section is considered — duplicates are technically invalid
41
+ * TOML and Codex rejects them, so cclaw does not try to be clever there.
42
+ */
43
+ export function classifyCodexHooksFlag(toml) {
44
+ if (toml === null) {
45
+ return "missing-file";
46
+ }
47
+ const lines = toml.split(/\r?\n/);
48
+ let inFeaturesSection = false;
49
+ let sawFeaturesHeader = false;
50
+ for (const rawLine of lines) {
51
+ const stripped = stripTomlComment(rawLine).trim();
52
+ if (stripped.length === 0)
53
+ continue;
54
+ const headerMatch = /^\[\s*([A-Za-z0-9_.-]+)\s*\]$/u.exec(stripped);
55
+ if (headerMatch) {
56
+ const section = headerMatch[1];
57
+ if (section === "features") {
58
+ inFeaturesSection = true;
59
+ sawFeaturesHeader = true;
60
+ }
61
+ else {
62
+ inFeaturesSection = false;
63
+ }
64
+ continue;
65
+ }
66
+ if (inFeaturesSection) {
67
+ const keyMatch = /^codex_hooks\s*=\s*(.*)$/u.exec(stripped);
68
+ if (keyMatch) {
69
+ const value = keyMatch[1].trim().toLowerCase();
70
+ return value === "true" ? "enabled" : "disabled";
71
+ }
72
+ }
73
+ }
74
+ if (sawFeaturesHeader)
75
+ return "missing-key";
76
+ return "missing-section";
77
+ }
78
+ function stripTomlComment(line) {
79
+ // Naive but sufficient for our narrow use case: we only read cclaw's
80
+ // own writes back, and cclaw never emits `=` after a `#` inside a
81
+ // string literal in config.toml. If a user has complex string values
82
+ // with `#` inside them, worst case we trip `classifyCodexHooksFlag`
83
+ // and prompt them again — non-destructive.
84
+ const hashIndex = line.indexOf("#");
85
+ return hashIndex === -1 ? line : line.slice(0, hashIndex);
86
+ }
87
+ /**
88
+ * Return a TOML document with `[features] codex_hooks = true` set.
89
+ * Preserves all other content verbatim:
90
+ * - If the document lacks a `[features]` section, we append one at the
91
+ * end of the file (separated by a blank line).
92
+ * - If `[features]` exists without `codex_hooks`, we insert the key
93
+ * immediately after the header.
94
+ * - If `codex_hooks` exists with any non-`true` value, we rewrite
95
+ * just that line.
96
+ * - If the flag is already `true`, the input is returned unchanged.
97
+ */
98
+ export function patchCodexHooksFlag(toml) {
99
+ const initial = toml ?? "";
100
+ const state = classifyCodexHooksFlag(toml);
101
+ if (state === "enabled") {
102
+ return { updated: initial, changed: false };
103
+ }
104
+ if (state === "missing-file" || state === "missing-section") {
105
+ const prefix = initial.length === 0
106
+ ? ""
107
+ : initial.endsWith("\n") ? initial : `${initial}\n`;
108
+ const separator = initial.trim().length === 0 ? "" : "\n";
109
+ const block = `${separator}[features]\ncodex_hooks = true\n`;
110
+ return { updated: `${prefix}${block}`, changed: true };
111
+ }
112
+ if (state === "missing-key") {
113
+ const updated = insertKeyInFeaturesSection(initial);
114
+ return { updated, changed: true };
115
+ }
116
+ const updated = replaceCodexHooksLineInFeaturesSection(initial);
117
+ return { updated, changed: true };
118
+ }
119
+ function insertKeyInFeaturesSection(toml) {
120
+ // Walk into `[features]`, remember the index of the last key / non-blank
121
+ // line inside that section, and splice `codex_hooks = true` immediately
122
+ // after it. This keeps the inserted key adjacent to existing features,
123
+ // never stranded after a blank line or pushed down past a later section
124
+ // header. If `[features]` is empty, we insert right after its header.
125
+ const lines = toml.split(/\r?\n/);
126
+ let inFeaturesSection = false;
127
+ let featuresHeaderIndex = -1;
128
+ let lastFeatureKeyIndex = -1;
129
+ for (let index = 0; index < lines.length; index += 1) {
130
+ const rawLine = lines[index];
131
+ const stripped = stripTomlComment(rawLine).trim();
132
+ const headerMatch = /^\[\s*([A-Za-z0-9_.-]+)\s*\]$/u.exec(stripped);
133
+ if (headerMatch) {
134
+ if (inFeaturesSection)
135
+ break;
136
+ if (headerMatch[1] === "features") {
137
+ inFeaturesSection = true;
138
+ featuresHeaderIndex = index;
139
+ lastFeatureKeyIndex = index;
140
+ }
141
+ continue;
142
+ }
143
+ if (inFeaturesSection && stripped.length > 0) {
144
+ lastFeatureKeyIndex = index;
145
+ }
146
+ }
147
+ if (featuresHeaderIndex === -1) {
148
+ // caller should have short-circuited before getting here; defensive
149
+ return toml;
150
+ }
151
+ lines.splice(lastFeatureKeyIndex + 1, 0, "codex_hooks = true");
152
+ const joined = lines.join("\n");
153
+ return joined.endsWith("\n") ? joined : `${joined}\n`;
154
+ }
155
+ function replaceCodexHooksLineInFeaturesSection(toml) {
156
+ const lines = toml.split(/\r?\n/);
157
+ let inFeaturesSection = false;
158
+ for (let index = 0; index < lines.length; index += 1) {
159
+ const rawLine = lines[index];
160
+ const stripped = stripTomlComment(rawLine).trim();
161
+ const headerMatch = /^\[\s*([A-Za-z0-9_.-]+)\s*\]$/u.exec(stripped);
162
+ if (headerMatch) {
163
+ inFeaturesSection = headerMatch[1] === "features";
164
+ continue;
165
+ }
166
+ if (inFeaturesSection && /^codex_hooks\s*=/u.test(stripped)) {
167
+ const indent = /^\s*/u.exec(rawLine)?.[0] ?? "";
168
+ lines[index] = `${indent}codex_hooks = true`;
169
+ }
170
+ }
171
+ const joined = lines.join("\n");
172
+ return joined.endsWith("\n") ? joined : `${joined}\n`;
173
+ }
174
+ /**
175
+ * Read the Codex config, return `null` when the file does not exist.
176
+ * All other read errors propagate so callers can surface a useful
177
+ * message instead of silently degrading.
178
+ */
179
+ export async function readCodexConfig(configPath) {
180
+ try {
181
+ return await fs.readFile(configPath, "utf8");
182
+ }
183
+ catch (err) {
184
+ if (err.code === "ENOENT") {
185
+ return null;
186
+ }
187
+ throw err;
188
+ }
189
+ }
190
+ export async function writeCodexConfig(configPath, content) {
191
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
192
+ await fs.writeFile(configPath, content, "utf8");
193
+ }
@@ -14,7 +14,7 @@ export declare const EVALS_ROOT = ".cclaw/evals";
14
14
  export declare const EVALS_CONFIG_PATH = ".cclaw/evals/config.yaml";
15
15
  export declare const EVALS_DIRS: readonly [".cclaw/evals", ".cclaw/evals/corpus", ".cclaw/evals/rubrics", ".cclaw/evals/baselines", ".cclaw/evals/reports"];
16
16
  export declare const REQUIRED_DIRS: readonly [".cclaw", ".cclaw/commands", ".cclaw/skills", ".cclaw/contexts", ".cclaw/templates", ".cclaw/artifacts", ".cclaw/worktrees", ".cclaw/state", ".cclaw/runs", ".cclaw/rules", ".cclaw/adapters", ".cclaw/agents", ".cclaw/hooks", ".cclaw/custom-skills", ".cclaw/evals", ".cclaw/evals/corpus", ".cclaw/evals/rubrics", ".cclaw/evals/baselines", ".cclaw/evals/reports"];
17
- export declare const REQUIRED_GITIGNORE_PATTERNS: readonly ["# cclaw generated artifacts", ".cclaw/", "# cclaw evals: user-owned, track in git", "!.cclaw/evals/", "!.cclaw/evals/config.yaml", "!.cclaw/evals/corpus/", "!.cclaw/evals/corpus/**", "!.cclaw/evals/rubrics/", "!.cclaw/evals/rubrics/**", "!.cclaw/evals/baselines/", "!.cclaw/evals/baselines/**", ".claude/commands/cc-*.md", ".claude/commands/cc.md", ".cursor/commands/cc-*.md", ".cursor/commands/cc.md", ".opencode/commands/cc-*.md", ".opencode/commands/cc.md", ".agents/skills/cclaw-cc/SKILL.md", ".agents/skills/cclaw-cc-*/SKILL.md", ".claude/hooks/hooks.json", ".cursor/hooks.json", ".opencode/plugins/cclaw-plugin.mjs", ".cursor/rules/cclaw-workflow.mdc"];
17
+ export declare const REQUIRED_GITIGNORE_PATTERNS: readonly ["# cclaw generated artifacts", ".cclaw/", "# cclaw evals: user-owned, track in git", "!.cclaw/evals/", "!.cclaw/evals/config.yaml", "!.cclaw/evals/corpus/", "!.cclaw/evals/corpus/**", "!.cclaw/evals/rubrics/", "!.cclaw/evals/rubrics/**", "!.cclaw/evals/baselines/", "!.cclaw/evals/baselines/**", ".claude/commands/cc-*.md", ".claude/commands/cc.md", ".cursor/commands/cc-*.md", ".cursor/commands/cc.md", ".opencode/commands/cc-*.md", ".opencode/commands/cc.md", ".agents/skills/cc/SKILL.md", ".agents/skills/cc-*/SKILL.md", ".claude/hooks/hooks.json", ".cursor/hooks.json", ".codex/hooks.json", ".opencode/plugins/cclaw-plugin.mjs", ".cursor/rules/cclaw-workflow.mdc"];
18
18
  export declare const COMMAND_FILE_ORDER: FlowStage[];
19
19
  export declare const UTILITY_COMMANDS: readonly ["learn", "next", "ideate", "view", "status", "tree", "diff", "ops", "feature", "tdd-log", "retro", "compound", "archive", "rewind"];
20
20
  export declare const SUBAGENT_SKILL_FOLDERS: readonly ["subagent-dev", "parallel-dispatch"];
package/dist/constants.js CHANGED
@@ -91,12 +91,15 @@ export const REQUIRED_GITIGNORE_PATTERNS = [
91
91
  ".cursor/commands/cc.md",
92
92
  ".opencode/commands/cc-*.md",
93
93
  ".opencode/commands/cc.md",
94
- // Codex uses skill-kind shims under `.agents/skills/cclaw-cc*/` since
95
- // v0.39.0; legacy `.codex/commands/*` is auto-cleaned on sync.
96
- ".agents/skills/cclaw-cc/SKILL.md",
97
- ".agents/skills/cclaw-cc-*/SKILL.md",
94
+ // Codex uses skill-kind shims under `.agents/skills/cc*/` since
95
+ // v0.40.0 (renamed from the `cclaw-cc*` layout in v0.39.0/v0.39.1).
96
+ // `cclaw sync` and `cclaw uninstall` both auto-remove the legacy
97
+ // `cclaw-cc*` directories.
98
+ ".agents/skills/cc/SKILL.md",
99
+ ".agents/skills/cc-*/SKILL.md",
98
100
  ".claude/hooks/hooks.json",
99
101
  ".cursor/hooks.json",
102
+ ".codex/hooks.json",
100
103
  ".opencode/plugins/cclaw-plugin.mjs",
101
104
  ".cursor/rules/cclaw-workflow.mdc"
102
105
  ];
@@ -192,28 +192,38 @@ has either a \`completed\` row with evidenceRefs (role-switch) or a
192
192
  const CODEX_PLAYBOOK = `---
193
193
  harness: codex
194
194
  fallback: role-switch
195
- description: "OpenAI Codex has no subagent dispatch and no hooks. cclaw ships entry points as skills under .agents/skills/; mandatory delegations fall back to role-switch with evidenceRefs."
195
+ description: "OpenAI Codex exposes lifecycle hooks (v0.114+, gated by the codex_hooks feature flag) but no subagent dispatch and no custom slash commands. cclaw ships entry points as skills under .agents/skills/cc*/ and wires .codex/hooks.json; mandatory delegations fall back to role-switch with evidenceRefs."
196
196
  ---
197
197
 
198
198
  # OpenAI Codex — Parity Playbook
199
199
 
200
- Codex CLI exposes **neither a custom slash-command system nor a hooks
201
- API**. cclaw v0.39.0 acknowledged this and rewired the codex harness:
200
+ Codex CLI has a different shape from Claude/Cursor:
202
201
 
203
202
  - **Entry points are skills.** \`/cc\`, \`/cc-next\`, \`/cc-ideate\`,
204
203
  \`/cc-view\`, \`/cc-ops\` are generated as skills at
205
- \`.agents/skills/cclaw-cc/SKILL.md\` (and \`cclaw-cc-next/\`, etc.). They
206
- activate via Codex's native \`/use <skillName>\` command or
207
- automatically when the user's prompt mentions any of the
208
- \`/cc\`-style tokens (skill descriptions include them verbatim).
209
- - **No hooks.** Everything that Claude/Cursor get from
210
- \`SessionStart\` / \`PreToolUse\` / \`PostToolUse\` / \`Stop\` /
211
- \`PreCompact\` must run as explicit agent steps. The session rehydration,
212
- prompt-guard, workflow-guard, context-monitor, and stop-checkpoint
213
- behaviors are documented in \`.cclaw/skills/using-cclaw/SKILL.md\`.
214
- - **Legacy paths are dead.** \`.codex/commands/*\` and \`.codex/hooks.json\`
215
- are removed on every \`cclaw sync\`. Do not restore them by hand —
216
- Codex CLI never read either path.
204
+ \`.agents/skills/cc/SKILL.md\` (and \`cc-next/\`, \`cc-view/\`,
205
+ \`cc-ideate/\`, \`cc-ops/\`). They activate via Codex's native
206
+ \`/use <skillName>\` command or automatically when the user's prompt
207
+ mentions any of the \`/cc\`-style tokens (skill descriptions include
208
+ them verbatim). Codex CLI removed custom prompts in v0.89 (Jan 2026);
209
+ there is no way to register a true custom slash command.
210
+ - **Lifecycle hooks.** Codex CLI v0.114 (Mar 2026) exposes lifecycle
211
+ hooks at \`.codex/hooks.json\`, gated behind the experimental
212
+ \`[features] codex_hooks = true\` flag in \`~/.codex/config.toml\`.
213
+ cclaw writes \`.codex/hooks.json\` on sync; if the flag is off, the
214
+ file is simply inert and \`cclaw doctor\` emits a warning. \`cclaw init\`
215
+ offers to patch the flag with explicit user consent.
216
+ - **Tool interception is Bash-only.** Codex's \`PreToolUse\` and
217
+ \`PostToolUse\` events only fire for the \`Bash\` tool. \`Write\`,
218
+ \`Edit\`, \`WebSearch\`, and MCP tool calls are **not** gated by hooks.
219
+ cclaw partially compensates by also wiring \`UserPromptSubmit\` to
220
+ \`prompt-guard.sh\` so the stage routing check fires before the turn
221
+ executes, but workflow-guard (TDD red-first, artifact presence) only
222
+ fires on Bash turns. See the hook coverage matrix below.
223
+ - **Legacy paths.** \`.codex/commands/*\` was never consumed by Codex and
224
+ is removed on every \`cclaw sync\`. The v0.39.x \`.agents/skills/cclaw-cc*/\`
225
+ layout is replaced by \`.agents/skills/cc*/\` and the old folders are
226
+ auto-removed on sync. Do not restore either by hand.
217
227
 
218
228
  ## Fallback: role-switch
219
229
 
@@ -243,33 +253,57 @@ disabled in v0.33 and remains off.
243
253
 
244
254
  ## Invocation cheatsheet
245
255
 
246
- - \`/use cclaw-cc\` — open the \`/cc\` skill and pick a track.
247
- - \`/use cclaw-cc-next\` — advance the flow one stage.
248
- - \`/use cclaw-cc-ops\` — compound / archive / rewind.
256
+ - \`/use cc\` — open the \`/cc\` skill and pick a track.
257
+ - \`/use cc-next\` — advance the flow one stage.
258
+ - \`/use cc-ops\` — compound / archive / rewind.
249
259
  - Typing \`/cc …\` or \`/cc-next …\` in plain text also works: Codex
250
260
  matches the skill descriptions (which spell out these tokens) and
251
261
  auto-loads the right skill body.
252
262
  - Use Codex's built-in \`/skill\` UI to enable or disable
253
263
  cclaw skills per session.
254
264
 
255
- ## Hook substitution matrix
265
+ ## Feature flag — how to enable hooks
256
266
 
257
- | Hook intent | Codex substitute |
258
- |-------------|------------------|
259
- | SessionStart rehydration | On first turn, the agent reads \`.cclaw/state/flow-state.json\` and \`.cclaw/knowledge.jsonl\` explicitly before acting. |
260
- | PreToolUse prompt-guard | The \`/cc\` skill body enforces task classification before writes. |
261
- | PreToolUse workflow-guard | The active stage skill enforces TDD / artifact gates before writes. |
262
- | PostToolUse context-monitor | End-of-turn budget check lives in \`.cclaw/references/protocols/ethos.md\`. |
263
- | Stop checkpoint | Stage-completion protocol updates \`.cclaw/state/flow-state.json\` in the same turn. |
264
- | PreCompact digest | Manual \`/cc-view status\` before \`/compact\`; the user triggers this. |
267
+ Codex CLI ignores \`.codex/hooks.json\` unless \`codex_hooks = true\`
268
+ appears under \`[features]\` in \`~/.codex/config.toml\`:
269
+
270
+ \`\`\`toml
271
+ [features]
272
+ codex_hooks = true
273
+ \`\`\`
274
+
275
+ \`cclaw init --codex\` prompts to write this automatically (one-line
276
+ diff, preserving the rest of \`config.toml\` untouched). Decline the
277
+ prompt to leave the file alone; the skill-level \`/use cc\` entry points
278
+ continue to work regardless.
279
+
280
+ ## Hook coverage matrix
281
+
282
+ | Hook intent | Codex mapping | Coverage |
283
+ |-------------|---------------|----------|
284
+ | SessionStart rehydration | \`SessionStart\` matcher \`startup|resume\` → \`session-start.sh\` | Full. |
285
+ | PreToolUse prompt-guard | \`PreToolUse\` matcher \`Bash\` + \`UserPromptSubmit\` → \`prompt-guard.sh\` | Bash tool calls are gated inline; \`UserPromptSubmit\` catches prompts before any tool fires, so non-Bash writes (\`Write\`/\`Edit\`) are still prompt-guarded at the turn boundary. |
286
+ | PreToolUse workflow-guard | \`PreToolUse\` matcher \`Bash\` → \`workflow-guard.sh\` | Bash-only. For \`Write\`/\`Edit\` calls the agent performs the TDD-order / artifact check in-turn (see the stage skill). |
287
+ | PostToolUse context-monitor | \`PostToolUse\` matcher \`Bash\` → \`context-monitor.sh\` | Bash-only. Other tool calls get context-monitored at end-of-turn via \`.cclaw/references/protocols/ethos.md\`. |
288
+ | Stop checkpoint | \`Stop\` → \`stop-checkpoint.sh\` | Full. |
289
+ | PreCompact digest | Not supported — Codex has no \`PreCompact\` event. | Covered by \`/cc-ops retro\` and the user running \`/cc-view status\` before Codex's \`/compact\` command. |
265
290
 
266
291
  ## Verification
267
292
 
268
293
  \`cclaw doctor\` on a codex-enabled install checks:
269
294
 
270
- - \`shim:codex:cclaw-cc:present\` and \`frontmatter\` (plus the four
271
- utility skills).
272
- - No legacy \`.codex/commands/\` or \`.codex/hooks.json\` lingering.
295
+ - \`shim:codex:cc:present\` and \`frontmatter\` (plus the four utility
296
+ skills \`cc-next\`, \`cc-view\`, \`cc-ops\`, \`cc-ideate\`).
297
+ - \`hook:schema:codex\` validates \`.codex/hooks.json\` shape.
298
+ - \`hook:wiring:codex\` verifies the generated hooks reference every
299
+ runtime script cclaw needs (session-start, prompt-guard, workflow-guard,
300
+ context-monitor, stop-checkpoint).
301
+ - \`warning:codex:feature_flag\` is emitted as a warning (not an error)
302
+ when \`~/.codex/config.toml\` is missing the \`codex_hooks\` feature
303
+ flag — hooks silently do nothing in that state.
304
+ - \`warning:codex:legacy_commands_dir\` and
305
+ \`warning:codex:legacy_cclaw_cc_skills\` catch leftovers from older
306
+ cclaw versions.
273
307
  - Every mandatory agent for the active stage has a \`completed\` row
274
308
  with \`fulfillmentMode: "role-switch"\` and at least one \`evidenceRef\`.
275
309
  `;
@@ -112,7 +112,7 @@ Harness-specific additions:
112
112
  - \`claude\`: \`.claude/commands/cc*.md\`, \`.claude/hooks/hooks.json\`
113
113
  - \`cursor\`: \`.cursor/commands/cc*.md\`, \`.cursor/hooks.json\`, \`.cursor/rules/cclaw-workflow.mdc\`
114
114
  - \`opencode\`: \`.opencode/commands/cc*.md\`, \`.opencode/plugins/cclaw-plugin.mjs\`, opencode plugin registration
115
- - \`codex\`: \`.agents/skills/cclaw-cc/SKILL.md\`, \`.agents/skills/cclaw-cc-next/SKILL.md\`, \`.agents/skills/cclaw-cc-ideate/SKILL.md\`, \`.agents/skills/cclaw-cc-view/SKILL.md\`, \`.agents/skills/cclaw-cc-ops/SKILL.md\` (Codex CLI reads \`.agents/skills/\` on startup; \`.codex/*\` was never consumed by the CLI and is auto-cleaned on sync)
115
+ - \`codex\`: \`.agents/skills/cc/SKILL.md\`, \`.agents/skills/cc-next/SKILL.md\`, \`.agents/skills/cc-ideate/SKILL.md\`, \`.agents/skills/cc-view/SKILL.md\`, \`.agents/skills/cc-ops/SKILL.md\`, \`.codex/hooks.json\` (Codex CLI reads \`.agents/skills/\` for custom skills and consumes \`.codex/hooks.json\` on v0.114+ when \`[features] codex_hooks = true\` is set in \`~/.codex/config.toml\`. \`.codex/commands/\` and the legacy \`.agents/skills/cclaw-cc*/\` layout from v0.39.x are auto-cleaned on sync.)
116
116
 
117
117
  ## Runtime observability
118
118
 
@@ -32,10 +32,16 @@ export const HOOK_EVENTS_BY_HARNESS = {
32
32
  precompact_digest: "plugin session.cleared/session.resumed hooks"
33
33
  },
34
34
  codex: {
35
- // Codex CLI has no hooks primitive. cclaw substitutes via skills
36
- // under `.agents/skills/cclaw-cc*/SKILL.md` plus explicit in-turn
37
- // agent steps (see codex playbook). All semantic events are
38
- // intentionally unmapped here so `harness-gaps.json` exposes them
39
- // honestly.
35
+ // Codex CLI v0.114+ exposes lifecycle hooks via `.codex/hooks.json`,
36
+ // gated by `[features] codex_hooks = true` in `~/.codex/config.toml`.
37
+ // SessionStart, Stop, and UserPromptSubmit fire for every turn;
38
+ // PreToolUse/PostToolUse are **Bash-only** (Write/Edit/WebSearch/MCP
39
+ // calls do not trigger them). `precompact_digest` is unmapped —
40
+ // Codex has no PreCompact event; cclaw covers it via `/cc-ops retro`.
41
+ session_rehydrate: "SessionStart matcher startup|resume",
42
+ pre_tool_prompt_guard: "PreToolUse matcher Bash -> prompt-guard.sh (plus UserPromptSubmit for non-Bash prompts)",
43
+ pre_tool_workflow_guard: "PreToolUse matcher Bash -> workflow-guard.sh (Bash-only)",
44
+ post_tool_context_monitor: "PostToolUse matcher Bash -> context-monitor.sh (Bash-only)",
45
+ stop_checkpoint: "Stop -> stop-checkpoint.sh"
40
46
  }
41
47
  };
@@ -1209,14 +1209,14 @@ Cclaw generates real hook integrations for every harness that exposes a
1209
1209
  hook primitive:
1210
1210
  - **Claude/Cursor:** lifecycle rehydration + PreToolUse/PostToolUse + Stop
1211
1211
  - **OpenCode:** session lifecycle + system transform rehydration + bootstrap parity (digest/warnings/knowledge snapshot)
1212
- - **Codex:** *no hooks API exists in Codex CLI* substitution happens via skills (\`.agents/skills/cclaw-cc*/SKILL.md\`) and explicit in-turn agent steps. See \`.cclaw/references/harnesses/codex-playbook.md\`.
1212
+ - **Codex:** Codex CLI ≥ v0.114 exposes lifecycle hooks at \`.codex/hooks.json\`, gated behind \`[features] codex_hooks = true\` in \`~/.codex/config.toml\`. \`PreToolUse\`/\`PostToolUse\` intercept **only the \`Bash\` tool** in Codex; \`Write\`/\`Edit\`/\`WebSearch\`/MCP calls are substituted via the \`/cc\` skill bodies under \`.agents/skills/cc*/SKILL.md\` and explicit in-turn agent steps. See \`.cclaw/references/harnesses/codex-playbook.md\` for the coverage matrix.
1213
1213
 
1214
1214
  | Harness | Hook file | Events |
1215
1215
  |---------|-----------|--------|
1216
1216
  | Claude Code | \`.claude/hooks/hooks.json\` | SessionStart(startup/resume/clear/compact), PreToolUse, PostToolUse, Stop |
1217
1217
  | Cursor | \`.cursor/hooks.json\` | sessionStart/sessionResume/sessionClear/sessionCompact, preToolUse, postToolUse, stop |
1218
1218
  | OpenCode | \`${RUNTIME_ROOT}/hooks/opencode-plugin.mjs\` | session.created/updated/resumed/cleared/compacted/idle, tool.execute.before/after, system transform |
1219
- | Codex | *none* | skill-description matching + in-turn agent steps (no hooks API) |
1219
+ | Codex | \`.codex/hooks.json\` | SessionStart(startup/resume), UserPromptSubmit, PreToolUse(Bash), PostToolUse(Bash), Stop (feature-gated by \`codex_hooks = true\`) |
1220
1220
 
1221
1221
  Hook state files:
1222
1222
  - \`${RUNTIME_ROOT}/state/stage-activity.jsonl\`
@@ -23,4 +23,23 @@ export declare function summarizeObservationsScript(): string;
23
23
  */
24
24
  export declare function claudeHooksJsonWithObservation(): string;
25
25
  export declare function cursorHooksJsonWithObservation(): string;
26
+ /**
27
+ * Codex CLI ≥ v0.114 hooks. Differences vs. the Claude shape:
28
+ *
29
+ * - `SessionStart` matcher is limited to `startup|resume` — Codex does
30
+ * not emit `clear` or `compact` lifecycle phases.
31
+ * - `PreToolUse` / `PostToolUse` fire **only for the `Bash` tool**
32
+ * (documented Codex limitation, v0.114/v0.115). We use the `Bash`
33
+ * matcher verbatim so Codex doesn't silently swallow our commands.
34
+ * - `UserPromptSubmit` is supported and is the closest analogue to
35
+ * Cursor's `preToolUse` for non-Bash tooling — we run prompt-guard
36
+ * there so workflow/prompt checks still fire when the tool being
37
+ * used is `Write` or `Edit` rather than `Bash`.
38
+ * - There is no `PreCompact` event in Codex CLI — pre-compact
39
+ * semantics are carried by the agent itself inside `/cc-ops retro`.
40
+ *
41
+ * The entire file is inert unless the user opts into
42
+ * `[features] codex_hooks = true` in `~/.codex/config.toml`; cclaw
43
+ * doctor and the init prompt handle that flag.
44
+ */
26
45
  export declare function codexHooksJsonWithObservation(): string;
@@ -1792,20 +1792,44 @@ export function cursorHooksJsonWithObservation() {
1792
1792
  }
1793
1793
  }, null, 2);
1794
1794
  }
1795
+ /**
1796
+ * Codex CLI ≥ v0.114 hooks. Differences vs. the Claude shape:
1797
+ *
1798
+ * - `SessionStart` matcher is limited to `startup|resume` — Codex does
1799
+ * not emit `clear` or `compact` lifecycle phases.
1800
+ * - `PreToolUse` / `PostToolUse` fire **only for the `Bash` tool**
1801
+ * (documented Codex limitation, v0.114/v0.115). We use the `Bash`
1802
+ * matcher verbatim so Codex doesn't silently swallow our commands.
1803
+ * - `UserPromptSubmit` is supported and is the closest analogue to
1804
+ * Cursor's `preToolUse` for non-Bash tooling — we run prompt-guard
1805
+ * there so workflow/prompt checks still fire when the tool being
1806
+ * used is `Write` or `Edit` rather than `Bash`.
1807
+ * - There is no `PreCompact` event in Codex CLI — pre-compact
1808
+ * semantics are carried by the agent itself inside `/cc-ops retro`.
1809
+ *
1810
+ * The entire file is inert unless the user opts into
1811
+ * `[features] codex_hooks = true` in `~/.codex/config.toml`; cclaw
1812
+ * doctor and the init prompt handle that flag.
1813
+ */
1795
1814
  export function codexHooksJsonWithObservation() {
1796
1815
  return JSON.stringify({
1797
1816
  cclawHookSchemaVersion: 1,
1798
1817
  hooks: {
1799
1818
  SessionStart: [{
1800
- matcher: "startup|resume|clear|compact",
1819
+ matcher: "startup|resume",
1801
1820
  hooks: [{
1802
1821
  type: "command",
1803
- command: `bash ${RUNTIME_ROOT}/hooks/session-start.sh`,
1804
- statusMessage: "Loading cclaw flow state"
1822
+ command: `bash ${RUNTIME_ROOT}/hooks/session-start.sh`
1823
+ }]
1824
+ }],
1825
+ UserPromptSubmit: [{
1826
+ hooks: [{
1827
+ type: "command",
1828
+ command: `bash ${RUNTIME_ROOT}/hooks/prompt-guard.sh`
1805
1829
  }]
1806
1830
  }],
1807
1831
  PreToolUse: [{
1808
- matcher: "*",
1832
+ matcher: "Bash",
1809
1833
  hooks: [{
1810
1834
  type: "command",
1811
1835
  command: `bash ${RUNTIME_ROOT}/hooks/prompt-guard.sh`
@@ -1815,7 +1839,7 @@ export function codexHooksJsonWithObservation() {
1815
1839
  }]
1816
1840
  }],
1817
1841
  PostToolUse: [{
1818
- matcher: "*",
1842
+ matcher: "Bash",
1819
1843
  hooks: [{
1820
1844
  type: "command",
1821
1845
  command: `bash ${RUNTIME_ROOT}/hooks/context-monitor.sh`
@@ -1827,14 +1851,6 @@ export function codexHooksJsonWithObservation() {
1827
1851
  command: `bash ${RUNTIME_ROOT}/hooks/stop-checkpoint.sh`,
1828
1852
  timeout: 10
1829
1853
  }]
1830
- }],
1831
- PreCompact: [{
1832
- matcher: "manual|auto",
1833
- hooks: [{
1834
- type: "command",
1835
- command: `bash ${RUNTIME_ROOT}/hooks/pre-compact.sh`,
1836
- timeout: 10
1837
- }]
1838
1854
  }]
1839
1855
  }
1840
1856
  }, null, 2);
package/dist/doctor.js CHANGED
@@ -20,6 +20,7 @@ import { reconcileAndWriteCurrentStageGateCatalog, verifyCompletedStagesGateClos
20
20
  import { parseTddCycleLog, validateTddCycleOrder } from "./tdd-cycle.js";
21
21
  import { stageSkillFolder } from "./content/skills.js";
22
22
  import { doctorCheckMetadata } from "./doctor-registry.js";
23
+ import { classifyCodexHooksFlag, codexConfigPath, readCodexConfig } from "./codex-feature-flag.js";
23
24
  import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LEGACY_LANGUAGE_RULE_PACK_FOLDERS, UTILITY_SKILL_FOLDERS } from "./content/utility-skills.js";
24
25
  import { CONTEXT_MODES, DEFAULT_CONTEXT_MODE } from "./content/contexts.js";
25
26
  import { DOCTOR_REFERENCE_MARKDOWN } from "./content/doctor-references.js";
@@ -635,16 +636,18 @@ export async function doctorChecks(projectRoot, options = {}) {
635
636
  });
636
637
  }
637
638
  }
638
- // Hook JSON files per harness. Codex is absent because Codex CLI has no
639
- // hooks primitive cclaw stopped writing `.codex/hooks.json` in v0.39.0.
640
- // OpenCode ships hooks through its plugin system (covered below).
639
+ // Hook JSON files per harness. OpenCode ships hooks through its plugin
640
+ // system (covered below). Codex joined the managed list in v0.40.0 — Codex
641
+ // CLI v0.114 consumes `.codex/hooks.json` behind the `codex_hooks`
642
+ // feature flag.
641
643
  const hookPaths = {
642
644
  claude: ".claude/hooks/hooks.json",
643
- cursor: ".cursor/hooks.json"
645
+ cursor: ".cursor/hooks.json",
646
+ codex: ".codex/hooks.json"
644
647
  };
645
648
  for (const harness of configuredHarnesses) {
646
649
  const hp = hookPaths[harness];
647
- if (!hp && harness !== "opencode" && harness !== "codex") {
650
+ if (!hp && harness !== "opencode") {
648
651
  checks.push({
649
652
  name: `hook:json:${harness}`,
650
653
  ok: false,
@@ -661,7 +664,7 @@ export async function doctorChecks(projectRoot, options = {}) {
661
664
  ok: hookOk,
662
665
  details: fullPath
663
666
  });
664
- if (harness === "claude" || harness === "cursor") {
667
+ if (harness === "claude" || harness === "cursor" || harness === "codex") {
665
668
  const schema = validateHookDocument(harness, parsed);
666
669
  checks.push({
667
670
  name: `hook:schema:${harness}`,
@@ -762,10 +765,11 @@ export async function doctorChecks(projectRoot, options = {}) {
762
765
  });
763
766
  }
764
767
  if (configuredHarnesses.includes("codex")) {
765
- // Codex CLI has no hooks primitive and no slash-command discovery
766
- // (`.codex/commands/*` was never read). cclaw ships codex shims as
767
- // skills under `.agents/skills/cclaw-cc*/SKILL.md`. Every required
768
- // skill must exist with the expected frontmatter `name`.
768
+ // Codex CLI has no custom slash-command discovery (`.codex/commands/*`
769
+ // was never read, even historically). cclaw ships codex entry points
770
+ // as skills under `.agents/skills/cc*/SKILL.md`; Codex v0.114+ also
771
+ // supports lifecycle hooks at `.codex/hooks.json` (gated by the
772
+ // `codex_hooks` feature flag in `~/.codex/config.toml`).
769
773
  const skillsRoot = path.join(projectRoot, ".agents/skills");
770
774
  for (const skillName of harnessShimSkillNames()) {
771
775
  const skillPath = path.join(skillsRoot, skillName, "SKILL.md");
@@ -791,26 +795,87 @@ export async function doctorChecks(projectRoot, options = {}) {
791
795
  : `${skillPath} absent; cannot validate frontmatter`
792
796
  });
793
797
  }
794
- // Warn if legacy `.codex/commands/*` or `.codex/hooks.json` is still
795
- // around cclaw syncs should have removed these, but a botched
796
- // upgrade or a manual restore could leave them dangling.
798
+ // Hook wiring: the generated `.codex/hooks.json` must reference every
799
+ // runtime script cclaw needs. Separate from the schema check above;
800
+ // schema covers structure, this check covers semantic wiring.
801
+ const codexHooksFile = path.join(projectRoot, ".codex/hooks.json");
802
+ const codexDoc = await readHookDocument(codexHooksFile);
803
+ const codexHooks = toObject(codexDoc?.hooks) ?? {};
804
+ const codexSessionCmds = collectHookCommands(codexHooks.SessionStart);
805
+ const codexPreCmds = collectHookCommands(codexHooks.PreToolUse);
806
+ const codexPostCmds = collectHookCommands(codexHooks.PostToolUse);
807
+ const codexStopCmds = collectHookCommands(codexHooks.Stop);
808
+ const codexWiringOk = codexSessionCmds.some((cmd) => cmd.includes("session-start.sh")) &&
809
+ codexPreCmds.some((cmd) => cmd.includes("prompt-guard.sh")) &&
810
+ codexPreCmds.some((cmd) => cmd.includes("workflow-guard.sh")) &&
811
+ codexPostCmds.some((cmd) => cmd.includes("context-monitor.sh")) &&
812
+ codexStopCmds.some((cmd) => cmd.includes("stop-checkpoint.sh"));
813
+ checks.push({
814
+ name: "hook:wiring:codex",
815
+ ok: codexWiringOk,
816
+ details: `${codexHooksFile} must wire session-start/prompt-guard/workflow-guard/context-monitor/stop-checkpoint (PreToolUse/PostToolUse run Bash-only in Codex v0.114+)`
817
+ });
818
+ // Feature flag warning: Codex ignores `.codex/hooks.json` unless the
819
+ // user has `[features] codex_hooks = true` in `~/.codex/config.toml`.
820
+ // Advisory warning — not a hard failure, because the skills still
821
+ // work without the flag.
822
+ const codexConfig = codexConfigPath();
823
+ let featureFlagNote = "";
824
+ try {
825
+ const content = await readCodexConfig(codexConfig);
826
+ const state = classifyCodexHooksFlag(content);
827
+ featureFlagNote =
828
+ state === "enabled"
829
+ ? `codex_hooks feature flag is enabled in ${codexConfig}`
830
+ : state === "missing-file"
831
+ ? `warning: ${codexConfig} does not exist; .codex/hooks.json will be ignored until you create it with \`[features]\\ncodex_hooks = true\\n\`.`
832
+ : state === "missing-section"
833
+ ? `warning: ${codexConfig} has no [features] section; add \`[features]\\ncodex_hooks = true\\n\` to enable cclaw hooks.`
834
+ : state === "missing-key"
835
+ ? `warning: ${codexConfig} is missing the codex_hooks key under [features]. Add \`codex_hooks = true\` to enable cclaw hooks.`
836
+ : `warning: ${codexConfig} sets codex_hooks to a non-true value; set \`codex_hooks = true\` under [features] to enable cclaw hooks.`;
837
+ }
838
+ catch (err) {
839
+ featureFlagNote = `warning: could not read ${codexConfig}: ${err instanceof Error ? err.message : String(err)}`;
840
+ }
841
+ checks.push({
842
+ name: "warning:codex:feature_flag",
843
+ ok: true,
844
+ details: featureFlagNote
845
+ });
846
+ // Legacy `.codex/commands/*` must not linger from older cclaw installs.
847
+ // (The `.codex/hooks.json` path is now managed and is validated above,
848
+ // so there is no longer a legacy_hooks_json warning.)
797
849
  const legacyCommandsDir = path.join(projectRoot, ".codex/commands");
798
850
  const legacyCommandsPresent = await exists(legacyCommandsDir);
799
851
  checks.push({
800
852
  name: "warning:codex:legacy_commands_dir",
801
853
  ok: true,
802
854
  details: legacyCommandsPresent
803
- ? `warning: ${legacyCommandsDir} still present; Codex never read this directory — run \`cclaw sync\` to remove it.`
855
+ ? `warning: ${legacyCommandsDir} still present; Codex never consumed this directory — run \`cclaw sync\` to remove it.`
804
856
  : `no legacy ${legacyCommandsDir} detected`
805
857
  });
806
- const legacyHooks = path.join(projectRoot, ".codex/hooks.json");
807
- const legacyHooksPresent = await exists(legacyHooks);
858
+ // Legacy v0.39.x skill layout under `.agents/skills/cclaw-cc*/`
859
+ // must have been removed — cclaw sync deletes these automatically,
860
+ // but flag leftovers so users notice an upgrade issue.
861
+ const legacyCodexSkills = [];
862
+ try {
863
+ const entries = await fs.readdir(skillsRoot);
864
+ for (const entry of entries) {
865
+ if (/^cclaw-cc(?:-.*)?$/u.test(entry)) {
866
+ legacyCodexSkills.push(entry);
867
+ }
868
+ }
869
+ }
870
+ catch {
871
+ // skills root absent; nothing to warn about
872
+ }
808
873
  checks.push({
809
- name: "warning:codex:legacy_hooks_json",
810
- ok: true,
811
- details: legacyHooksPresent
812
- ? `warning: ${legacyHooks} still present; Codex CLI has no hooks API run \`cclaw sync\` to remove it.`
813
- : `no legacy ${legacyHooks} detected`
874
+ name: "warning:codex:legacy_cclaw_cc_skills",
875
+ ok: legacyCodexSkills.length === 0,
876
+ details: legacyCodexSkills.length === 0
877
+ ? `no legacy cclaw-cc* skill folders detected under .agents/skills/`
878
+ : `warning: legacy skill folders from cclaw v0.39.x present (${legacyCodexSkills.join(", ")}); run \`cclaw sync\` to remove them.`
814
879
  });
815
880
  }
816
881
  if (configuredHarnesses.includes("opencode")) {
@@ -14,35 +14,48 @@ const RUNTIME_AGENTS_BLOCK_GLOBAL_PATTERN = new RegExp(RUNTIME_AGENTS_BLOCK_SOUR
14
14
  const UTILITY_SHIMS = [
15
15
  {
16
16
  fileName: "cc-next.md",
17
- skillName: "cclaw-cc-next",
17
+ skillName: "cc-next",
18
18
  command: "next",
19
19
  skillFolder: "flow-next-step",
20
20
  commandFile: "next.md"
21
21
  },
22
22
  {
23
23
  fileName: "cc-ideate.md",
24
- skillName: "cclaw-cc-ideate",
24
+ skillName: "cc-ideate",
25
25
  command: "ideate",
26
26
  skillFolder: "flow-ideate",
27
27
  commandFile: "ideate.md"
28
28
  },
29
29
  {
30
30
  fileName: "cc-view.md",
31
- skillName: "cclaw-cc-view",
31
+ skillName: "cc-view",
32
32
  command: "view",
33
33
  skillFolder: "flow-view",
34
34
  commandFile: "view.md"
35
35
  },
36
36
  {
37
37
  fileName: "cc-ops.md",
38
- skillName: "cclaw-cc-ops",
38
+ skillName: "cc-ops",
39
39
  command: "ops",
40
40
  skillFolder: "flow-ops",
41
41
  commandFile: "ops.md"
42
42
  }
43
43
  ];
44
44
  /** Skill-kind shim name for the root `/cc` entry point. */
45
- const ENTRY_SHIM_SKILL_NAME = "cclaw-cc";
45
+ const ENTRY_SHIM_SKILL_NAME = "cc";
46
+ /**
47
+ * Skill directory names that v0.39.0 / v0.39.1 installed under
48
+ * `.agents/skills/` before the rename. We delete these on every sync so
49
+ * upgrades from those versions do not leave orphaned `cclaw-cc*`
50
+ * folders that would double-register in Codex's skill listing.
51
+ */
52
+ const LEGACY_CODEX_SKILL_NAMES = [
53
+ "cclaw-cc",
54
+ "cclaw-cc-next",
55
+ "cclaw-cc-view",
56
+ "cclaw-cc-ops",
57
+ "cclaw-cc-ideate"
58
+ ];
46
59
  /**
47
60
  * Shims that older cclaw versions installed as top-level slash commands but
48
61
  * which we now treat as internal (skill-only, invoked by the agent, never
@@ -98,16 +111,20 @@ export const HARNESS_ADAPTERS = {
98
111
  codex: {
99
112
  id: "codex",
100
113
  // Codex CLI reads skills from the universal `.agents/skills/` path
101
- // (OpenAI Codex 0.89, Jan 2026; legacy `~/.codex/skills/` also
102
- // supported). It has no native `.codex/commands/` slash-command
103
- // discovery and no `.codex/hooks.json` primitive v0.39.0 migrated
104
- // cclaw to write skill-kind shims here and stops generating the
105
- // dead `.codex/*` surfaces.
114
+ // (OpenAI Codex 0.89, Jan 2026). It does NOT have a native
115
+ // `.codex/commands/*` slash-command discovery — cclaw installs
116
+ // its entry points as skills here. Since v0.114 (Mar 2026) Codex
117
+ // also exposes lifecycle hooks via `.codex/hooks.json`, behind
118
+ // the `[features] codex_hooks = true` feature flag in
119
+ // `~/.codex/config.toml`. cclaw writes that file on sync and
120
+ // `hookSurface: "limited"` records the reality: SessionStart /
121
+ // UserPromptSubmit / Stop fire for every turn, but PreToolUse /
122
+ // PostToolUse only intercept the `Bash` tool.
106
123
  commandDir: ".agents/skills",
107
124
  shimKind: "skill",
108
125
  capabilities: {
109
126
  nativeSubagentDispatch: "none",
110
- hookSurface: "none",
127
+ hookSurface: "limited",
111
128
  structuredAsk: "plain-text",
112
129
  subagentFallback: "role-switch"
113
130
  }
@@ -122,6 +139,7 @@ export function harnessTier(harnessId) {
122
139
  }
123
140
  if (capabilities.hookSurface === "full" ||
124
141
  capabilities.hookSurface === "plugin" ||
142
+ capabilities.hookSurface === "limited" ||
125
143
  capabilities.nativeSubagentDispatch === "generic" ||
126
144
  capabilities.nativeSubagentDispatch === "partial") {
127
145
  return "tier2";
@@ -209,26 +227,33 @@ If the same approach fails three times in a row (same command, same finding, sam
209
227
  ### Detail Level
210
228
 
211
229
  - This managed AGENTS block is intentionally minimal for cross-project use.
212
- - Harness coverage is tiered: Tier1 (claude), Tier2 (cursor/opencode/codex), Tier3 (fallback/manual-only).
230
+ - Harness coverage is tiered: Tier1 (claude), Tier2 (cursor/opencode/codex — codex has Bash-only tool hooks), Tier3 (fallback/manual-only).
213
231
  - Detailed operating procedures live in \`.cclaw/skills/using-cclaw/SKILL.md\`.
214
232
  - Preamble budget and cooldown rules live in \`.cclaw/references/protocols/ethos.md\`.
215
233
  - Subagent orchestration patterns: \`.cclaw/skills/subagent-dev/SKILL.md\` and \`.cclaw/skills/parallel-dispatch/SKILL.md\`.
216
234
 
217
235
  ### Codex users
218
236
 
219
- OpenAI Codex CLI has **no native \`/cc\` slash command** and **no hooks API**. The
220
- \`/cc\`, \`/cc-next\`, \`/cc-ideate\`, \`/cc-view\`, \`/cc-ops\` tokens above describe
221
- intent in Codex they map onto skills cclaw installs at
222
- \`.agents/skills/cclaw-cc*/SKILL.md\`. Activate one of two ways:
237
+ OpenAI Codex CLI has **no native \`/cc\` slash command** (custom prompts
238
+ were deprecated in v0.89, Jan 2026). The \`/cc\`, \`/cc-next\`,
239
+ \`/cc-ideate\`, \`/cc-view\`, \`/cc-ops\` tokens above describe intent in
240
+ Codex they map onto skills cclaw installs at
241
+ \`.agents/skills/cc*/SKILL.md\`. Activate one of two ways:
223
242
 
224
- - Type \`/use cclaw-cc\` (or \`cclaw-cc-next\`, etc.) at Codex's prompt.
243
+ - Type \`/use cc\` (or \`cc-next\`, etc.) at Codex's prompt.
225
244
  - Type \`/cc …\` as plain text — Codex matches the skill \`description\`
226
245
  frontmatter (which spells out the token verbatim) and loads the right
227
246
  skill body automatically.
228
247
 
229
- Legacy \`.codex/commands/*\` and \`.codex/hooks.json\` are removed on
230
- \`cclaw sync\` Codex CLI never consumed either path. See
231
- \`.cclaw/references/harnesses/codex-playbook.md\` for the hook-substitution matrix.
248
+ Codex CLI v0.114+ (Mar 2026) **does** expose lifecycle hooks via
249
+ \`.codex/hooks.json\`, gated by the \`[features] codex_hooks = true\` flag
250
+ in \`~/.codex/config.toml\`. cclaw generates \`.codex/hooks.json\` on
251
+ sync; if the feature flag is off, hooks are inert and cclaw's
252
+ session-start rehydration simply does not fire. Run \`cclaw doctor\` to
253
+ see if the flag is missing. \`.codex/commands/*\` is still unused by
254
+ Codex CLI and is removed on every sync. See
255
+ \`.cclaw/references/harnesses/codex-playbook.md\` for the hook coverage
256
+ matrix (Bash-only \`PreToolUse\`/\`PostToolUse\`; other events are full).
232
257
  ${CCLAW_MARKER_END}`;
233
258
  }
234
259
  /** Removes the cclaw AGENTS.md block. */
@@ -336,14 +361,22 @@ function codexSkillBody(command, skillFolder, commandFile) {
336
361
  const extraContractHeading = command === "cc"
337
362
  ? "If you have not already loaded the cclaw meta-skill this session, also load `.cclaw/skills/using-cclaw/SKILL.md` — it is the routing brain for stage/utility selection."
338
363
  : "This skill is a utility entry point, not a flow stage. Do not mutate `.cclaw/state/flow-state.json` directly.";
364
+ const skillSlug = command === "cc" ? "cc" : `cc-${command}`;
339
365
  return `# ${title}
340
366
 
341
367
  You are running inside the OpenAI Codex harness. Codex has **no native
342
- \`${slashToken}\` slash command and no \`.codex/hooks.json\` primitive** cclaw
343
- ships its entry points as skills under \`.agents/skills/\` and relies on
344
- \`AGENTS.md\` + skill descriptions for activation. If the user typed
345
- \`${slashToken} …\` as plain text (or asked to perform its action in English),
346
- follow the steps below.
368
+ \`${slashToken}\` slash command** custom prompts were deprecated in
369
+ Codex CLI v0.89 (Jan 2026). cclaw ships its entry points as skills
370
+ under \`.agents/skills/${skillSlug}/\` so the user can either:
371
+
372
+ - Type \`/use ${skillSlug}\` at the Codex prompt, or
373
+ - Type \`${slashToken} …\` (or describe the intent in English) — Codex's
374
+ skill matcher picks this skill up via the description frontmatter.
375
+
376
+ Lifecycle hooks **are** available in Codex CLI v0.114+ (behind the
377
+ \`[features] codex_hooks = true\` flag in \`~/.codex/config.toml\`) and
378
+ cclaw installs a matching \`.codex/hooks.json\` — see the playbook for
379
+ what the hook surface does and does not cover.
347
380
 
348
381
  ## Protocol
349
382
 
@@ -363,10 +396,11 @@ follow the steps below.
363
396
  append a completed row with \`evidenceRefs\` to
364
397
  \`.cclaw/state/delegation-log.json\`. Silent auto-waiver is disabled
365
398
  (v0.33+).
366
- - Codex has no hooks. Session rehydration, prompt-guard, workflow-guard,
367
- context-monitor, stop-checkpoint, and pre-compact behavior all have to
368
- run as explicit agent steps. Read \`.cclaw/references/harnesses/codex-playbook.md\`
369
- for the substitution matrix.
399
+ - Codex's \`PreToolUse\` / \`PostToolUse\` hooks currently only intercept
400
+ the \`Bash\` tool. \`Write\`, \`Edit\`, \`WebSearch\`, and MCP tool calls
401
+ are **not** gated by hooks read
402
+ \`.cclaw/references/harnesses/codex-playbook.md\` for what cclaw
403
+ substitutes with in-turn agent steps for those call classes.
370
404
  `;
371
405
  }
372
406
  function codexSkillMarkdown(command, skillName, skillFolder, commandFile) {
@@ -406,9 +440,16 @@ async function writeSkillKindShims(commandDir) {
406
440
  }
407
441
  /**
408
442
  * Legacy codex surfaces cclaw wrote before v0.39.0 that Codex CLI never
409
- * actually consumed (`.codex/commands/*.md` had no discovery, `.codex/hooks.json`
410
- * had no hooks API). On every sync we proactively delete these so users
411
- * upgrading from older installs see a clean `.codex/` (or no `.codex/` at all).
443
+ * consumed (`.codex/commands/*.md` had no discovery primitive). We keep
444
+ * removing `.codex/commands/` on every sync so upgrades from those
445
+ * installs leave a clean slate, but as of v0.40.0 we DO write
446
+ * `.codex/hooks.json` again — Codex CLI grew a real hooks API in
447
+ * v0.114.0 (Mar 2026), and that file is the current, supported target.
448
+ *
449
+ * This function also removes skill folders named after the old
450
+ * `cclaw-cc*` scheme (v0.39.0 / v0.39.1) now that cclaw installs them
451
+ * as plain `cc*`. Leaving them around would make Codex list two skills
452
+ * for the same entry point.
412
453
  */
413
454
  async function cleanupLegacyCodexSurfaces(projectRoot) {
414
455
  const legacyCommandsDir = path.join(projectRoot, ".codex/commands");
@@ -418,16 +459,21 @@ async function cleanupLegacyCodexSurfaces(projectRoot) {
418
459
  catch {
419
460
  // best-effort cleanup
420
461
  }
421
- const legacyHooksFile = path.join(projectRoot, ".codex/hooks.json");
422
- try {
423
- await fs.rm(legacyHooksFile, { force: true });
424
- }
425
- catch {
426
- // best-effort cleanup
462
+ // Remove the old `cclaw-cc*` skill folders if they exist from a
463
+ // previous cclaw install. Idempotent; best-effort.
464
+ const legacySkillsRoot = path.join(projectRoot, ".agents/skills");
465
+ for (const name of LEGACY_CODEX_SKILL_NAMES) {
466
+ const folder = path.join(legacySkillsRoot, name);
467
+ try {
468
+ await fs.rm(folder, { recursive: true, force: true });
469
+ }
470
+ catch {
471
+ // best-effort
472
+ }
427
473
  }
428
- // If `.codex/` is now empty we drop it entirely codex CLI doesn't need
429
- // that directory anymore. Leave it alone if the user stored their own
430
- // data there.
474
+ // If `.codex/` is now empty we drop it — happens when neither hooks
475
+ // are enabled nor the user has their own state there. Otherwise we
476
+ // leave the directory alone.
431
477
  try {
432
478
  const codexDir = path.join(projectRoot, ".codex");
433
479
  const entries = await fs.readdir(codexDir);
package/dist/install.js CHANGED
@@ -23,7 +23,7 @@ import { archiveCommandContract, archiveCommandSkillMarkdown } from "./content/a
23
23
  import { rewindCommandContract, rewindCommandSkillMarkdown } from "./content/rewind-command.js";
24
24
  import { subagentDrivenDevSkill, parallelAgentsSkill } from "./content/subagents.js";
25
25
  import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
26
- import { sessionStartScript, stopCheckpointScript, preCompactScript, opencodePluginJs, claudeHooksJson, cursorHooksJson } from "./content/hooks.js";
26
+ import { sessionStartScript, stopCheckpointScript, preCompactScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
27
27
  import { contextMonitorScript, promptGuardScript, workflowGuardScript } from "./content/observe.js";
28
28
  import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
29
29
  import { decisionProtocolMarkdown, completionProtocolMarkdown, ethosProtocolMarkdown } from "./content/protocols.js";
@@ -667,10 +667,18 @@ async function writeHooks(projectRoot, config) {
667
667
  await ensureDir(cursorDir);
668
668
  await writeMergedHookJson(projectRoot, path.join(cursorDir, "hooks.json"), cursorHooksJson());
669
669
  }
670
- // Codex has no hooks primitive — v0.39.0 stopped generating
671
- // `.codex/hooks.json` because Codex CLI never actually read it. Codex
672
- // substitutes for hooks via explicit agent steps documented in the
673
- // codex playbook.
670
+ else if (harness === "codex") {
671
+ // Codex CLI v0.114 (Mar 2026) supports lifecycle hooks at
672
+ // `.codex/hooks.json`, gated behind the `[features] codex_hooks = true`
673
+ // flag in `~/.codex/config.toml`. cclaw always writes the file so
674
+ // the moment the flag flips on, the cclaw hooks start firing. See
675
+ // `codexHooksJsonWithObservation` for the Bash-only caveat on
676
+ // PreToolUse/PostToolUse. `cclaw doctor` warns if the feature flag
677
+ // is not set.
678
+ const codexDir = path.join(projectRoot, ".codex");
679
+ await ensureDir(codexDir);
680
+ await writeMergedHookJson(projectRoot, path.join(codexDir, "hooks.json"), codexHooksJson());
681
+ }
674
682
  // OpenCode registration is auto-managed via opencode.json/opencode.jsonc.
675
683
  }
676
684
  }
@@ -905,13 +913,14 @@ async function writeCursorWorkflowRule(projectRoot, harnesses) {
905
913
  }
906
914
  async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
907
915
  const enabled = new Set(harnesses);
908
- // Codex is intentionally absent cclaw stopped generating `.codex/hooks.json`
909
- // in v0.39.0 (the file was never consumed by Codex CLI). Legacy `.codex/*`
910
- // files are removed unconditionally by `cleanupLegacyCodexSurfaces` during
911
- // every `syncHarnessShims` pass.
916
+ // v0.40.0: `.codex/hooks.json` is back on the managed list now that
917
+ // Codex CLI actually consumes it (v0.114+, Mar 2026). Legacy
918
+ // `.codex/commands/` cleanup still happens unconditionally from
919
+ // `cleanupLegacyCodexSurfaces` inside `syncHarnessShims`.
912
920
  const managedHookFiles = [
913
921
  { harness: "claude", hookPath: path.join(projectRoot, ".claude/hooks/hooks.json") },
914
- { harness: "cursor", hookPath: path.join(projectRoot, ".cursor/hooks.json") }
922
+ { harness: "cursor", hookPath: path.join(projectRoot, ".cursor/hooks.json") },
923
+ { harness: "codex", hookPath: path.join(projectRoot, ".codex/hooks.json") }
915
924
  ];
916
925
  for (const entry of managedHookFiles) {
917
926
  if (enabled.has(entry.harness))
@@ -1306,15 +1315,19 @@ export async function uninstallCclaw(projectRoot) {
1306
1315
  // directory not present
1307
1316
  }
1308
1317
  }
1309
- // v0.39.0 migrated Codex shims to `.agents/skills/cclaw-cc*/SKILL.md`
1310
- // (Codex CLI reads `.agents/skills/`, not `.codex/commands/`). On uninstall
1311
- // we remove just the cclaw-owned skill folders, not the whole
1312
- // `.agents/skills/` directory other tools may share it.
1318
+ // Codex shim location history:
1319
+ // - < v0.39.0: `.codex/commands/cc*.md` (never consumed by Codex CLI)
1320
+ // - v0.39.0 / v0.39.1: `.agents/skills/cclaw-cc*/SKILL.md`
1321
+ // - ≥ v0.40.0: `.agents/skills/cc*/SKILL.md` (matches Codex's `/use cc`
1322
+ // prompt verbatim)
1323
+ // Remove all three legacy layouts on uninstall so orphans can't linger.
1324
+ // We only touch cclaw-owned folder names — other tools share
1325
+ // `.agents/skills/` with us.
1313
1326
  const codexSkillsRoot = path.join(projectRoot, ".agents/skills");
1314
1327
  try {
1315
1328
  const entries = await fs.readdir(codexSkillsRoot);
1316
1329
  for (const entry of entries) {
1317
- if (/^cclaw-(?:cc)(?:-.*)?$/u.test(entry)) {
1330
+ if (/^(?:cclaw-)?cc(?:-(?:next|view|ops|ideate))?$/u.test(entry)) {
1318
1331
  await fs.rm(path.join(codexSkillsRoot, entry), { recursive: true, force: true });
1319
1332
  }
1320
1333
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.39.1",
3
+ "version": "0.40.0",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {