cclaw-cli 0.39.1 → 0.41.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/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")) {
@@ -58,7 +58,23 @@ export interface HarnessAdapter {
58
58
  */
59
59
  nativeSubagentDispatch: "full" | "generic" | "partial" | "none";
60
60
  hookSurface: "full" | "plugin" | "limited" | "none";
61
- structuredAsk: "AskUserQuestion" | "AskQuestion" | "plain-text";
61
+ /**
62
+ * Structured-ask primitive exposed by the harness.
63
+ *
64
+ * - `AskUserQuestion` — Claude Code native tool (≤5 options × multi-question).
65
+ * - `AskQuestion` — Cursor native tool (≥2 options, multi-question, `allow_multiple`).
66
+ * - `question` — OpenCode native tool (header + options + "type custom"
67
+ * fallback); **gated**: requires `permission.question: "allow"` in
68
+ * `opencode.json`, and for ACP clients additionally needs
69
+ * `OPENCODE_ENABLE_QUESTION_TOOL=1`.
70
+ * - `request_user_input` — Codex CLI tool (1-3 short questions); experimental
71
+ * and primarily surfaced inside Plan / Collaboration mode templates
72
+ * (`codex-rs/collaboration-mode-templates`). Available to agents running
73
+ * inside Codex but may be hidden on very old builds.
74
+ * - `plain-text` — fallback only; used when no native primitive is
75
+ * available (no shipping harness uses this in v0.41.0).
76
+ */
77
+ structuredAsk: "AskUserQuestion" | "AskQuestion" | "question" | "request_user_input" | "plain-text";
62
78
  /**
63
79
  * Declared fallback pattern used when the harness cannot satisfy a
64
80
  * mandatory delegation natively. Drives `checkMandatoryDelegations`
@@ -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
@@ -91,24 +104,44 @@ export const HARNESS_ADAPTERS = {
91
104
  capabilities: {
92
105
  nativeSubagentDispatch: "partial",
93
106
  hookSurface: "plugin",
94
- structuredAsk: "plain-text",
107
+ // OpenCode exposes a native `question` tool (header + options +
108
+ // custom-answer fallback, multi-question navigation). It is
109
+ // permission-gated — `opencode.json` must set
110
+ // `permission.question: "allow"` and ACP clients must export
111
+ // `OPENCODE_ENABLE_QUESTION_TOOL=1`. cclaw surfaces the tool name
112
+ // in the Decision Protocol and in the OpenCode playbook; skills
113
+ // fall back to the shared plain-text lettered list when the tool
114
+ // is denied or unavailable.
115
+ structuredAsk: "question",
95
116
  subagentFallback: "role-switch"
96
117
  }
97
118
  },
98
119
  codex: {
99
120
  id: "codex",
100
121
  // 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.
122
+ // (OpenAI Codex 0.89, Jan 2026). It does NOT have a native
123
+ // `.codex/commands/*` slash-command discovery — cclaw installs
124
+ // its entry points as skills here. Since v0.114 (Mar 2026) Codex
125
+ // also exposes lifecycle hooks via `.codex/hooks.json`, behind
126
+ // the `[features] codex_hooks = true` feature flag in
127
+ // `~/.codex/config.toml`. cclaw writes that file on sync and
128
+ // `hookSurface: "limited"` records the reality: SessionStart /
129
+ // UserPromptSubmit / Stop fire for every turn, but PreToolUse /
130
+ // PostToolUse only intercept the `Bash` tool.
106
131
  commandDir: ".agents/skills",
107
132
  shimKind: "skill",
108
133
  capabilities: {
109
134
  nativeSubagentDispatch: "none",
110
- hookSurface: "none",
111
- structuredAsk: "plain-text",
135
+ hookSurface: "limited",
136
+ // Codex CLI exposes `request_user_input` — an experimental tool
137
+ // that asks 1-3 short questions and returns the user's answers.
138
+ // It is the primitive the built-in Plan / Collaboration mode
139
+ // templates use (see `codex-rs/collaboration-mode-templates`).
140
+ // Agents running inside Codex can call it directly; cclaw wires
141
+ // it into the Decision Protocol and the Codex playbook. The
142
+ // shared plain-text lettered list is the documented fallback
143
+ // when the tool is unavailable.
144
+ structuredAsk: "request_user_input",
112
145
  subagentFallback: "role-switch"
113
146
  }
114
147
  }
@@ -122,6 +155,7 @@ export function harnessTier(harnessId) {
122
155
  }
123
156
  if (capabilities.hookSurface === "full" ||
124
157
  capabilities.hookSurface === "plugin" ||
158
+ capabilities.hookSurface === "limited" ||
125
159
  capabilities.nativeSubagentDispatch === "generic" ||
126
160
  capabilities.nativeSubagentDispatch === "partial") {
127
161
  return "tier2";
@@ -209,26 +243,33 @@ If the same approach fails three times in a row (same command, same finding, sam
209
243
  ### Detail Level
210
244
 
211
245
  - 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).
246
+ - Harness coverage is tiered: Tier1 (claude), Tier2 (cursor/opencode/codex — codex has Bash-only tool hooks), Tier3 (fallback/manual-only).
213
247
  - Detailed operating procedures live in \`.cclaw/skills/using-cclaw/SKILL.md\`.
214
248
  - Preamble budget and cooldown rules live in \`.cclaw/references/protocols/ethos.md\`.
215
249
  - Subagent orchestration patterns: \`.cclaw/skills/subagent-dev/SKILL.md\` and \`.cclaw/skills/parallel-dispatch/SKILL.md\`.
216
250
 
217
251
  ### Codex users
218
252
 
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:
253
+ OpenAI Codex CLI has **no native \`/cc\` slash command** (custom prompts
254
+ were deprecated in v0.89, Jan 2026). The \`/cc\`, \`/cc-next\`,
255
+ \`/cc-ideate\`, \`/cc-view\`, \`/cc-ops\` tokens above describe intent in
256
+ Codex they map onto skills cclaw installs at
257
+ \`.agents/skills/cc*/SKILL.md\`. Activate one of two ways:
223
258
 
224
- - Type \`/use cclaw-cc\` (or \`cclaw-cc-next\`, etc.) at Codex's prompt.
259
+ - Type \`/use cc\` (or \`cc-next\`, etc.) at Codex's prompt.
225
260
  - Type \`/cc …\` as plain text — Codex matches the skill \`description\`
226
261
  frontmatter (which spells out the token verbatim) and loads the right
227
262
  skill body automatically.
228
263
 
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.
264
+ Codex CLI v0.114+ (Mar 2026) **does** expose lifecycle hooks via
265
+ \`.codex/hooks.json\`, gated by the \`[features] codex_hooks = true\` flag
266
+ in \`~/.codex/config.toml\`. cclaw generates \`.codex/hooks.json\` on
267
+ sync; if the feature flag is off, hooks are inert and cclaw's
268
+ session-start rehydration simply does not fire. Run \`cclaw doctor\` to
269
+ see if the flag is missing. \`.codex/commands/*\` is still unused by
270
+ Codex CLI and is removed on every sync. See
271
+ \`.cclaw/references/harnesses/codex-playbook.md\` for the hook coverage
272
+ matrix (Bash-only \`PreToolUse\`/\`PostToolUse\`; other events are full).
232
273
  ${CCLAW_MARKER_END}`;
233
274
  }
234
275
  /** Removes the cclaw AGENTS.md block. */
@@ -336,14 +377,22 @@ function codexSkillBody(command, skillFolder, commandFile) {
336
377
  const extraContractHeading = command === "cc"
337
378
  ? "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
379
  : "This skill is a utility entry point, not a flow stage. Do not mutate `.cclaw/state/flow-state.json` directly.";
380
+ const skillSlug = command === "cc" ? "cc" : `cc-${command}`;
339
381
  return `# ${title}
340
382
 
341
383
  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.
384
+ \`${slashToken}\` slash command** custom prompts were deprecated in
385
+ Codex CLI v0.89 (Jan 2026). cclaw ships its entry points as skills
386
+ under \`.agents/skills/${skillSlug}/\` so the user can either:
387
+
388
+ - Type \`/use ${skillSlug}\` at the Codex prompt, or
389
+ - Type \`${slashToken} …\` (or describe the intent in English) — Codex's
390
+ skill matcher picks this skill up via the description frontmatter.
391
+
392
+ Lifecycle hooks **are** available in Codex CLI v0.114+ (behind the
393
+ \`[features] codex_hooks = true\` flag in \`~/.codex/config.toml\`) and
394
+ cclaw installs a matching \`.codex/hooks.json\` — see the playbook for
395
+ what the hook surface does and does not cover.
347
396
 
348
397
  ## Protocol
349
398
 
@@ -363,10 +412,11 @@ follow the steps below.
363
412
  append a completed row with \`evidenceRefs\` to
364
413
  \`.cclaw/state/delegation-log.json\`. Silent auto-waiver is disabled
365
414
  (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.
415
+ - Codex's \`PreToolUse\` / \`PostToolUse\` hooks currently only intercept
416
+ the \`Bash\` tool. \`Write\`, \`Edit\`, \`WebSearch\`, and MCP tool calls
417
+ are **not** gated by hooks read
418
+ \`.cclaw/references/harnesses/codex-playbook.md\` for what cclaw
419
+ substitutes with in-turn agent steps for those call classes.
370
420
  `;
371
421
  }
372
422
  function codexSkillMarkdown(command, skillName, skillFolder, commandFile) {
@@ -406,9 +456,16 @@ async function writeSkillKindShims(commandDir) {
406
456
  }
407
457
  /**
408
458
  * 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).
459
+ * consumed (`.codex/commands/*.md` had no discovery primitive). We keep
460
+ * removing `.codex/commands/` on every sync so upgrades from those
461
+ * installs leave a clean slate, but as of v0.40.0 we DO write
462
+ * `.codex/hooks.json` again — Codex CLI grew a real hooks API in
463
+ * v0.114.0 (Mar 2026), and that file is the current, supported target.
464
+ *
465
+ * This function also removes skill folders named after the old
466
+ * `cclaw-cc*` scheme (v0.39.0 / v0.39.1) now that cclaw installs them
467
+ * as plain `cc*`. Leaving them around would make Codex list two skills
468
+ * for the same entry point.
412
469
  */
413
470
  async function cleanupLegacyCodexSurfaces(projectRoot) {
414
471
  const legacyCommandsDir = path.join(projectRoot, ".codex/commands");
@@ -418,16 +475,21 @@ async function cleanupLegacyCodexSurfaces(projectRoot) {
418
475
  catch {
419
476
  // best-effort cleanup
420
477
  }
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
478
+ // Remove the old `cclaw-cc*` skill folders if they exist from a
479
+ // previous cclaw install. Idempotent; best-effort.
480
+ const legacySkillsRoot = path.join(projectRoot, ".agents/skills");
481
+ for (const name of LEGACY_CODEX_SKILL_NAMES) {
482
+ const folder = path.join(legacySkillsRoot, name);
483
+ try {
484
+ await fs.rm(folder, { recursive: true, force: true });
485
+ }
486
+ catch {
487
+ // best-effort
488
+ }
427
489
  }
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.
490
+ // If `.codex/` is now empty we drop it — happens when neither hooks
491
+ // are enabled nor the user has their own state there. Otherwise we
492
+ // leave the directory alone.
431
493
  try {
432
494
  const codexDir = path.join(projectRoot, ".codex");
433
495
  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))
@@ -975,8 +984,24 @@ async function writeHarnessGapsState(projectRoot, harnesses) {
975
984
  remediation.push(`subagent dispatch → record explicit harness_limitation waiver; no parity path available`);
976
985
  break;
977
986
  }
978
- if (capabilities.structuredAsk === "plain-text") {
979
- remediation.push("structured ask fall back to a numbered plain-text list; first option is default");
987
+ // Per-harness structuredAsk remediation: record either the fallback
988
+ // requirement (plain-text) or the gating / experimental status of the
989
+ // native primitive so `cclaw doctor` and harness-gaps.json stay
990
+ // honest about *why* a primitive might silently not fire.
991
+ switch (capabilities.structuredAsk) {
992
+ case "plain-text":
993
+ remediation.push("structured ask → fall back to a numbered plain-text list; first option is default");
994
+ break;
995
+ case "question":
996
+ remediation.push(`structured ask → OpenCode \`question\` tool; enable with \`permission.question: "allow"\` in \`opencode.json\` (ACP clients additionally need \`OPENCODE_ENABLE_QUESTION_TOOL=1\`). Fallback: shared plain-text lettered list.`);
997
+ break;
998
+ case "request_user_input":
999
+ remediation.push("structured ask → Codex `request_user_input` tool (experimental; surfaced in Plan / Collaboration mode). Fallback: shared plain-text lettered list when the tool is hidden.");
1000
+ break;
1001
+ case "AskUserQuestion":
1002
+ case "AskQuestion":
1003
+ // Native first-class ask — no remediation required.
1004
+ break;
980
1005
  }
981
1006
  for (const event of missingHookEvents) {
982
1007
  remediation.push(`hook event ${event} → schedule the corresponding script manually or accept reduced observability`);
@@ -1306,15 +1331,19 @@ export async function uninstallCclaw(projectRoot) {
1306
1331
  // directory not present
1307
1332
  }
1308
1333
  }
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.
1334
+ // Codex shim location history:
1335
+ // - < v0.39.0: `.codex/commands/cc*.md` (never consumed by Codex CLI)
1336
+ // - v0.39.0 / v0.39.1: `.agents/skills/cclaw-cc*/SKILL.md`
1337
+ // - ≥ v0.40.0: `.agents/skills/cc*/SKILL.md` (matches Codex's `/use cc`
1338
+ // prompt verbatim)
1339
+ // Remove all three legacy layouts on uninstall so orphans can't linger.
1340
+ // We only touch cclaw-owned folder names — other tools share
1341
+ // `.agents/skills/` with us.
1313
1342
  const codexSkillsRoot = path.join(projectRoot, ".agents/skills");
1314
1343
  try {
1315
1344
  const entries = await fs.readdir(codexSkillsRoot);
1316
1345
  for (const entry of entries) {
1317
- if (/^cclaw-(?:cc)(?:-.*)?$/u.test(entry)) {
1346
+ if (/^(?:cclaw-)?cc(?:-(?:next|view|ops|ideate))?$/u.test(entry)) {
1318
1347
  await fs.rm(path.join(codexSkillsRoot, entry), { recursive: true, force: true });
1319
1348
  }
1320
1349
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.39.1",
3
+ "version": "0.41.0",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {