cclaw-cli 0.38.0 → 0.39.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,8 +127,10 @@ 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
- - `.codex/commands/cc*.md` + `.codex/hooks.json`
131
- - `AGENTS.md` with a managed routing block
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)
133
+ - `AGENTS.md` with a managed routing block (includes a Codex-specific note)
132
134
 
133
135
  `.cclaw/config.yaml` holds every tunable key (prompt guard strictness,
134
136
  TDD enforcement, git-hook guards, language rule packs, track heuristics).
@@ -355,7 +357,7 @@ closes every real gap with a documented fallback — not a silent waiver.
355
357
  | Claude Code | full (named subagents) | `native` | full | `AskUserQuestion` | [`claude-playbook.md`](./src/content/harness-playbooks.ts) |
356
358
  | Cursor | generic Task dispatcher | `generic-dispatch` | full | `AskQuestion` | `cursor-playbook.md` |
357
359
  | OpenCode | plugin / in-session | `role-switch` | plugin | plain-text | `opencode-playbook.md` |
358
- | OpenAI Codex | in-session only | `role-switch` (evidenceRefs required) | full | plain-text | `codex-playbook.md` |
360
+ | OpenAI Codex | in-session only | `role-switch` (evidenceRefs required) | none (no hooks API) | plain-text | `codex-playbook.md` |
359
361
 
360
362
  What the fallbacks mean:
361
363
 
@@ -378,6 +380,17 @@ What the fallbacks mean:
378
380
  harness declares it. Currently unused — v0.33 removed the old
379
381
  Codex-only auto-waiver path.
380
382
 
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.
393
+
381
394
  The full capability matrix lives in
382
395
  [`docs/harnesses.md`](./docs/harnesses.md). Per-harness playbooks are
383
396
  generated into `.cclaw/references/harnesses/` on every install and
package/dist/cli.js CHANGED
@@ -151,7 +151,12 @@ function buildInitSurfacePreview(harnesses) {
151
151
  ];
152
152
  for (const harness of harnesses) {
153
153
  const adapter = HARNESS_ADAPTERS[harness];
154
- lines.push(`${adapter.commandDir}/cc*.md`);
154
+ if (adapter.shimKind === "skill") {
155
+ lines.push(`${adapter.commandDir}/cclaw-cc*/SKILL.md`);
156
+ }
157
+ else {
158
+ lines.push(`${adapter.commandDir}/cc*.md`);
159
+ }
155
160
  if (harness === "claude") {
156
161
  lines.push(".claude/hooks/hooks.json");
157
162
  }
@@ -159,9 +164,9 @@ function buildInitSurfacePreview(harnesses) {
159
164
  lines.push(".cursor/hooks.json");
160
165
  lines.push(".cursor/rules/cclaw-workflow.mdc");
161
166
  }
162
- if (harness === "codex") {
163
- lines.push(".codex/hooks.json");
164
- }
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.
165
170
  if (harness === "opencode") {
166
171
  lines.push(".opencode/plugins/cclaw-plugin.mjs");
167
172
  lines.push("opencode.json(.c) plugin registration");
@@ -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", ".codex/commands/cc-*.md", ".codex/commands/cc.md", ".claude/hooks/hooks.json", ".cursor/hooks.json", ".codex/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/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"];
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,11 +91,12 @@ 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/commands/cc-*.md",
95
- ".codex/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",
96
98
  ".claude/hooks/hooks.json",
97
99
  ".cursor/hooks.json",
98
- ".codex/hooks.json",
99
100
  ".opencode/plugins/cclaw-plugin.mjs",
100
101
  ".cursor/rules/cclaw-workflow.mdc"
101
102
  ];
@@ -192,28 +192,43 @@ 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 primitive. cclaw uses role-switch with evidenceRefs; silent auto-waiver is explicitly disabled."
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."
196
196
  ---
197
197
 
198
198
  # OpenAI Codex — Parity Playbook
199
199
 
200
- **Fallback: role-switch.** Codex has no subagent dispatch neither named
201
- nor generic. cclaw used to silently auto-waive mandatory delegations on
202
- Codex; v0.33 disables that shortcut. The agent must role-switch in-session
203
- and record evidence, or the delegation gate blocks stage completion.
204
-
205
- ## Role-switch protocol
206
-
207
- Identical to OpenCode. Key requirements:
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:
202
+
203
+ - **Entry points are skills.** \`/cc\`, \`/cc-next\`, \`/cc-ideate\`,
204
+ \`/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.
217
+
218
+ ## Fallback: role-switch
219
+
220
+ Codex has no subagent dispatch — neither named nor generic. Mandatory
221
+ delegations must be role-switched in-session. Silent auto-waiver was
222
+ disabled in v0.33 and remains off.
208
223
 
209
224
  1. **Explicit announce.** Before performing the role, emit a single
210
225
  message naming the role and citing \`.cclaw/agents/<agent>.md\`.
211
- 2. **No role interleaving.** Do not mix, for example, reviewer and
212
- test-author work into the same turn close one delegation before
213
- opening another.
214
- 3. **EvidenceRefs are mandatory.** Under Codex's role-switch fallback a
215
- \`completed\` row without \`evidenceRefs\` is treated as
216
- \`missingEvidence\` by \`cclaw doctor\` and blocks the gate.
226
+ 2. **No role interleaving.** Close one delegation before opening
227
+ another; never mix, for example, reviewer and test-author work in
228
+ the same turn.
229
+ 3. **EvidenceRefs are mandatory.** A \`completed\` row without
230
+ \`evidenceRefs\` is treated as \`missingEvidence\` by \`cclaw doctor\`
231
+ and blocks the stage gate.
217
232
 
218
233
  ## Stage-specific role maps
219
234
 
@@ -226,23 +241,37 @@ Identical to OpenCode. Key requirements:
226
241
  | review | \`reviewer\`, \`security-reviewer\` | \`.cclaw/artifacts/07-review.md\` |
227
242
  | ship | \`doc-updater\` | \`.cclaw/artifacts/08-ship.md\` |
228
243
 
229
- ## Why no auto-waiver anymore?
244
+ ## Invocation cheatsheet
230
245
 
231
- Silent auto-waiver on Codex let entire stages complete without any
232
- reviewer or test-author work. That defeats cclaw's hard gates. v0.33
233
- replaces it with an explicit role-switch obligation: the agent still gets
234
- a path forward, but the path is visible in the delegation log.
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.
249
+ - Typing \`/cc …\` or \`/cc-next …\` in plain text also works: Codex
250
+ matches the skill descriptions (which spell out these tokens) and
251
+ auto-loads the right skill body.
252
+ - Use Codex's built-in \`/skill\` UI to enable or disable
253
+ cclaw skills per session.
235
254
 
236
- If a team genuinely wants to skip a delegation on Codex, they must
237
- manually append a \`status: "waived"\` row with a one-line
238
- \`waiverReason\` the same audit trail any Claude/Cursor install would
239
- need.
255
+ ## Hook substitution matrix
256
+
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. |
240
265
 
241
266
  ## Verification
242
267
 
243
- \`cclaw doctor\` passes when every mandatory agent for the active stage
244
- has a \`completed\` row with \`fulfillmentMode: "role-switch"\` and at
245
- least one \`evidenceRef\`.
268
+ \`cclaw doctor\` on a codex-enabled install checks:
269
+
270
+ - \`shim:codex:cclaw-cc:present\` and \`frontmatter\` (plus the four
271
+ utility skills).
272
+ - No legacy \`.codex/commands/\` or \`.codex/hooks.json\` lingering.
273
+ - Every mandatory agent for the active stage has a \`completed\` row
274
+ with \`fulfillmentMode: "role-switch"\` and at least one \`evidenceRef\`.
246
275
  `;
247
276
  const PLAYBOOK_BY_HARNESS = {
248
277
  claude: CLAUDE_PLAYBOOK,
@@ -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\`: \`.codex/commands/cc*.md\`, \`.codex/hooks.json\`
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)
116
116
 
117
117
  ## Runtime observability
118
118
 
@@ -32,11 +32,10 @@ export const HOOK_EVENTS_BY_HARNESS = {
32
32
  precompact_digest: "plugin session.cleared/session.resumed hooks"
33
33
  },
34
34
  codex: {
35
- session_rehydrate: "SessionStart matcher startup|resume|clear|compact",
36
- pre_tool_prompt_guard: "PreToolUse -> prompt-guard.sh",
37
- pre_tool_workflow_guard: "PreToolUse -> workflow-guard.sh",
38
- post_tool_context_monitor: "PostToolUse -> context-monitor.sh",
39
- stop_checkpoint: "Stop -> stop-checkpoint.sh",
40
- precompact_digest: "PreCompact -> pre-compact.sh"
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.
41
40
  }
42
41
  };
@@ -1205,16 +1205,18 @@ export default function cclawPlugin(ctx) {
1205
1205
  export function hooksAgentsMdBlock() {
1206
1206
  return `### Hooks (real lifecycle integration)
1207
1207
 
1208
- Cclaw generates real hook integrations across harnesses:
1209
- - **Claude/Cursor/Codex:** lifecycle rehydration + PreToolUse/PostToolUse + Stop
1208
+ Cclaw generates real hook integrations for every harness that exposes a
1209
+ hook primitive:
1210
+ - **Claude/Cursor:** lifecycle rehydration + PreToolUse/PostToolUse + Stop
1210
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\`.
1211
1213
 
1212
1214
  | Harness | Hook file | Events |
1213
1215
  |---------|-----------|--------|
1214
1216
  | Claude Code | \`.claude/hooks/hooks.json\` | SessionStart(startup/resume/clear/compact), PreToolUse, PostToolUse, Stop |
1215
1217
  | Cursor | \`.cursor/hooks.json\` | sessionStart/sessionResume/sessionClear/sessionCompact, preToolUse, postToolUse, stop |
1216
- | Codex | \`.codex/hooks.json\` | SessionStart(startup/resume/clear/compact), PreToolUse, PostToolUse, Stop |
1217
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) |
1218
1220
 
1219
1221
  Hook state files:
1220
1222
  - \`${RUNTIME_ROOT}/state/stage-activity.jsonl\`
package/dist/doctor.js CHANGED
@@ -8,7 +8,7 @@ import { CCLAW_AGENTS } from "./content/core-agents.js";
8
8
  import { readConfig } from "./config.js";
9
9
  import { exists } from "./fs-utils.js";
10
10
  import { gitignoreHasRequiredPatterns } from "./gitignore.js";
11
- import { HARNESS_ADAPTERS, CCLAW_MARKER_START, CCLAW_MARKER_END, harnessShimFileNames } from "./harness-adapters.js";
11
+ import { HARNESS_ADAPTERS, CCLAW_MARKER_START, CCLAW_MARKER_END, harnessShimFileNames, harnessShimSkillNames } from "./harness-adapters.js";
12
12
  import { policyChecks } from "./policy.js";
13
13
  import { readFlowState } from "./runs.js";
14
14
  import { skippedStagesForTrack } from "./flow-state.js";
@@ -474,13 +474,17 @@ export async function doctorChecks(projectRoot, options = {}) {
474
474
  });
475
475
  continue;
476
476
  }
477
- for (const shim of harnessShimFileNames()) {
478
- const shimPath = path.join(projectRoot, adapter.commandDir, shim);
479
- checks.push({
480
- name: `shim:${harness}:${shim.replace(".md", "")}`,
481
- ok: await exists(shimPath),
482
- details: shimPath
483
- });
477
+ // For command-kind harnesses we check flat files; skill-kind (codex) is
478
+ // validated in the codex-specific block below (`shim:codex:<name>:*`).
479
+ if (adapter.shimKind === "command") {
480
+ for (const shim of harnessShimFileNames()) {
481
+ const shimPath = path.join(projectRoot, adapter.commandDir, shim);
482
+ checks.push({
483
+ name: `shim:${harness}:${shim.replace(".md", "")}`,
484
+ ok: await exists(shimPath),
485
+ details: shimPath
486
+ });
487
+ }
484
488
  }
485
489
  const playbookFile = path.join(projectRoot, RUNTIME_ROOT, ...HARNESS_PLAYBOOKS_DIR.split("/"), harnessPlaybookFileName(harness));
486
490
  checks.push({
@@ -631,15 +635,16 @@ export async function doctorChecks(projectRoot, options = {}) {
631
635
  });
632
636
  }
633
637
  }
634
- // Hook JSON files per harness
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).
635
641
  const hookPaths = {
636
642
  claude: ".claude/hooks/hooks.json",
637
- cursor: ".cursor/hooks.json",
638
- codex: ".codex/hooks.json"
643
+ cursor: ".cursor/hooks.json"
639
644
  };
640
645
  for (const harness of configuredHarnesses) {
641
646
  const hp = hookPaths[harness];
642
- if (!hp && harness !== "opencode") {
647
+ if (!hp && harness !== "opencode" && harness !== "codex") {
643
648
  checks.push({
644
649
  name: `hook:json:${harness}`,
645
650
  ok: false,
@@ -656,7 +661,7 @@ export async function doctorChecks(projectRoot, options = {}) {
656
661
  ok: hookOk,
657
662
  details: fullPath
658
663
  });
659
- if (harness === "claude" || harness === "cursor" || harness === "codex") {
664
+ if (harness === "claude" || harness === "cursor") {
660
665
  const schema = validateHookDocument(harness, parsed);
661
666
  checks.push({
662
667
  name: `hook:schema:${harness}`,
@@ -757,29 +762,55 @@ export async function doctorChecks(projectRoot, options = {}) {
757
762
  });
758
763
  }
759
764
  if (configuredHarnesses.includes("codex")) {
760
- const file = path.join(projectRoot, ".codex/hooks.json");
761
- const parsed = await readHookDocument(file);
762
- const hooks = toObject(parsed?.hooks) ?? {};
763
- const sessionStart = hooks.SessionStart;
764
- const ok = JSON.stringify(sessionStart ?? "").includes("startup|resume|clear|compact");
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`.
769
+ const skillsRoot = path.join(projectRoot, ".agents/skills");
770
+ for (const skillName of harnessShimSkillNames()) {
771
+ const skillPath = path.join(skillsRoot, skillName, "SKILL.md");
772
+ let ok = false;
773
+ let frontmatterOk = false;
774
+ if (await exists(skillPath)) {
775
+ ok = true;
776
+ const content = await fs.readFile(skillPath, "utf8");
777
+ frontmatterOk = new RegExp(`^---[\\s\\S]*?\\nname: ${skillName}\\b`, "u").test(content);
778
+ }
779
+ checks.push({
780
+ name: `shim:codex:${skillName}:present`,
781
+ ok,
782
+ details: skillPath
783
+ });
784
+ checks.push({
785
+ name: `shim:codex:${skillName}:frontmatter`,
786
+ ok,
787
+ details: frontmatterOk
788
+ ? `${skillPath} has \`name: ${skillName}\` frontmatter`
789
+ : ok
790
+ ? `${skillPath} present but \`name: ${skillName}\` frontmatter is missing`
791
+ : `${skillPath} absent; cannot validate frontmatter`
792
+ });
793
+ }
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.
797
+ const legacyCommandsDir = path.join(projectRoot, ".codex/commands");
798
+ const legacyCommandsPresent = await exists(legacyCommandsDir);
765
799
  checks.push({
766
- name: "lifecycle:codex:rehydration_matcher",
767
- ok,
768
- details: `${file} must include SessionStart matcher startup|resume|clear|compact`
800
+ name: "warning:codex:legacy_commands_dir",
801
+ ok: true,
802
+ details: legacyCommandsPresent
803
+ ? `warning: ${legacyCommandsDir} still present; Codex never read this directory — run \`cclaw sync\` to remove it.`
804
+ : `no legacy ${legacyCommandsDir} detected`
769
805
  });
770
- const sessionCommands = collectHookCommands(hooks.SessionStart);
771
- const preCommands = collectHookCommands(hooks.PreToolUse);
772
- const postCommands = collectHookCommands(hooks.PostToolUse);
773
- const stopCommands = collectHookCommands(hooks.Stop);
774
- const wiringOk = sessionCommands.some((cmd) => cmd.includes("session-start.sh")) &&
775
- preCommands.some((cmd) => cmd.includes("prompt-guard.sh")) &&
776
- preCommands.some((cmd) => cmd.includes("workflow-guard.sh")) &&
777
- postCommands.some((cmd) => cmd.includes("context-monitor.sh")) &&
778
- stopCommands.some((cmd) => cmd.includes("stop-checkpoint.sh"));
806
+ const legacyHooks = path.join(projectRoot, ".codex/hooks.json");
807
+ const legacyHooksPresent = await exists(legacyHooks);
779
808
  checks.push({
780
- name: "hook:wiring:codex",
781
- ok: wiringOk,
782
- details: `${file} must wire session-start/prompt-guard/workflow-guard/context-monitor/stop-checkpoint`
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`
783
814
  });
784
815
  }
785
816
  if (configuredHarnesses.includes("opencode")) {
@@ -21,9 +21,32 @@ export type SubagentFallback =
21
21
  * under `waiverReason: "harness_limitation"`.
22
22
  */
23
23
  | "waiver";
24
+ /**
25
+ * How a harness discovers cclaw's `/cc*` entry points.
26
+ *
27
+ * - `command` — harness has a native custom slash-command system and reads
28
+ * flat markdown files from `<commandDir>/<fileName>.md` (Claude Code,
29
+ * Cursor, OpenCode).
30
+ * - `skill` — harness ignores flat commands and reads SKILL.md from
31
+ * directories under a skills root (Codex CLI ≥0.89, Jan 2026). cclaw
32
+ * writes `<commandDir>/<skillName>/SKILL.md` and the agent invokes it
33
+ * either via `/use <skillName>` or via automatic description matching
34
+ * when the user's text mentions `/cc`, `/cc-next`, etc.
35
+ */
36
+ export type ShimKind = "command" | "skill";
24
37
  export interface HarnessAdapter {
25
38
  id: HarnessId;
39
+ /**
40
+ * Root directory where cclaw writes `/cc*` entry points.
41
+ *
42
+ * - For `shimKind: "command"` this is the directory containing flat
43
+ * markdown files (`<commandDir>/cc.md`, `<commandDir>/cc-next.md`, …).
44
+ * - For `shimKind: "skill"` this is the skills root that contains
45
+ * per-skill subdirectories (`<commandDir>/<skillName>/SKILL.md`).
46
+ */
26
47
  commandDir: string;
48
+ /** See {@link ShimKind}. Defaults to `"command"` if unspecified at a callsite. */
49
+ shimKind: ShimKind;
27
50
  capabilities: {
28
51
  /**
29
52
  * Level of native subagent dispatch:
@@ -45,6 +68,8 @@ export interface HarnessAdapter {
45
68
  };
46
69
  }
47
70
  export declare function harnessShimFileNames(): string[];
71
+ /** Skill folder names cclaw writes under `<commandDir>` for skill-kind harnesses. */
72
+ export declare function harnessShimSkillNames(): string[];
48
73
  export declare const HARNESS_ADAPTERS: Record<HarnessId, HarnessAdapter>;
49
74
  export type HarnessTier = "tier1" | "tier2" | "tier3";
50
75
  export declare function harnessTier(harnessId: HarnessId): HarnessTier;
@@ -14,29 +14,35 @@ 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
18
  command: "next",
18
19
  skillFolder: "flow-next-step",
19
20
  commandFile: "next.md"
20
21
  },
21
22
  {
22
23
  fileName: "cc-ideate.md",
24
+ skillName: "cclaw-cc-ideate",
23
25
  command: "ideate",
24
26
  skillFolder: "flow-ideate",
25
27
  commandFile: "ideate.md"
26
28
  },
27
29
  {
28
30
  fileName: "cc-view.md",
31
+ skillName: "cclaw-cc-view",
29
32
  command: "view",
30
33
  skillFolder: "flow-view",
31
34
  commandFile: "view.md"
32
35
  },
33
36
  {
34
37
  fileName: "cc-ops.md",
38
+ skillName: "cclaw-cc-ops",
35
39
  command: "ops",
36
40
  skillFolder: "flow-ops",
37
41
  commandFile: "ops.md"
38
42
  }
39
43
  ];
44
+ /** Skill-kind shim name for the root `/cc` entry point. */
45
+ const ENTRY_SHIM_SKILL_NAME = "cclaw-cc";
40
46
  /**
41
47
  * Shims that older cclaw versions installed as top-level slash commands but
42
48
  * which we now treat as internal (skill-only, invoked by the agent, never
@@ -47,10 +53,15 @@ const LEGACY_HARNESS_SHIMS = ["cc-learn.md"];
47
53
  export function harnessShimFileNames() {
48
54
  return ["cc.md", ...UTILITY_SHIMS.map((shim) => shim.fileName)];
49
55
  }
56
+ /** Skill folder names cclaw writes under `<commandDir>` for skill-kind harnesses. */
57
+ export function harnessShimSkillNames() {
58
+ return [ENTRY_SHIM_SKILL_NAME, ...UTILITY_SHIMS.map((shim) => shim.skillName)];
59
+ }
50
60
  export const HARNESS_ADAPTERS = {
51
61
  claude: {
52
62
  id: "claude",
53
63
  commandDir: ".claude/commands",
64
+ shimKind: "command",
54
65
  capabilities: {
55
66
  nativeSubagentDispatch: "full",
56
67
  hookSurface: "full",
@@ -61,6 +72,7 @@ export const HARNESS_ADAPTERS = {
61
72
  cursor: {
62
73
  id: "cursor",
63
74
  commandDir: ".cursor/commands",
75
+ shimKind: "command",
64
76
  capabilities: {
65
77
  // Cursor has a real Task tool with subagent_type (generalPurpose,
66
78
  // explore, shell, browser-use, …) but no user-defined named
@@ -75,6 +87,7 @@ export const HARNESS_ADAPTERS = {
75
87
  opencode: {
76
88
  id: "opencode",
77
89
  commandDir: ".opencode/commands",
90
+ shimKind: "command",
78
91
  capabilities: {
79
92
  nativeSubagentDispatch: "partial",
80
93
  hookSurface: "plugin",
@@ -84,10 +97,17 @@ export const HARNESS_ADAPTERS = {
84
97
  },
85
98
  codex: {
86
99
  id: "codex",
87
- commandDir: ".codex/commands",
100
+ // 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.
106
+ commandDir: ".agents/skills",
107
+ shimKind: "skill",
88
108
  capabilities: {
89
109
  nativeSubagentDispatch: "none",
90
- hookSurface: "full",
110
+ hookSurface: "none",
91
111
  structuredAsk: "plain-text",
92
112
  subagentFallback: "role-switch"
93
113
  }
@@ -193,6 +213,22 @@ If the same approach fails three times in a row (same command, same finding, sam
193
213
  - Detailed operating procedures live in \`.cclaw/skills/using-cclaw/SKILL.md\`.
194
214
  - Preamble budget and cooldown rules live in \`.cclaw/references/protocols/ethos.md\`.
195
215
  - Subagent orchestration patterns: \`.cclaw/skills/subagent-dev/SKILL.md\` and \`.cclaw/skills/parallel-dispatch/SKILL.md\`.
216
+
217
+ ### Codex users
218
+
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:
223
+
224
+ - Type \`/use cclaw-cc\` (or \`cclaw-cc-next\`, etc.) at Codex's prompt.
225
+ - Type \`/cc …\` as plain text — Codex matches the skill \`description\`
226
+ frontmatter (which spells out the token verbatim) and loads the right
227
+ skill body automatically.
228
+
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.
196
232
  ${CCLAW_MARKER_END}`;
197
233
  }
198
234
  /** Removes the cclaw AGENTS.md block. */
@@ -266,6 +302,143 @@ Load and execute:
266
302
  This is a utility command (not a flow stage). It does not advance flow state.
267
303
  `;
268
304
  }
305
+ /**
306
+ * Frontmatter `description` that triggers the skill when the user types any
307
+ * of the classic cclaw slash-tokens. Codex's skill matcher runs on the skill
308
+ * description verbatim, so we spell out every vocabulary Codex users type
309
+ * instead of relying on semantics.
310
+ */
311
+ function codexSkillDescription(command) {
312
+ switch (command) {
313
+ case "cc":
314
+ return `Entry point for the cclaw 8-stage workflow (brainstorm → scope → design → spec → plan → tdd → review → ship). Use whenever the user types \`/cc\`, \`/cclaw\`, or asks to "start the flow", "begin cclaw", "kick off the workflow", "classify this task", or wants to start/resume a non-trivial software change. No args = resume the active stage from \`.cclaw/state/flow-state.json\`. With a prompt = classify and pick a track (quick/medium/standard).`;
315
+ case "next":
316
+ return `Advance the cclaw flow to the next stage. Use when the user types \`/cc-next\` or asks to "move to the next stage", "continue the flow", "advance cclaw", "progress the workflow", or when the current stage skill reports completion and gates have passed.`;
317
+ case "ideate":
318
+ return `Read-only repo-improvement discovery for cclaw. Use when the user types \`/cc-ideate\` or asks to "ideate", "brainstorm improvements", "scan the repo for TODOs/tech debt", "generate a backlog", or wants a ranked list of candidate ideas before committing to a single flow. Does not mutate \`.cclaw/state/flow-state.json\`.`;
319
+ case "view":
320
+ return `Read-only router for cclaw flow views. Use when the user types \`/cc-view\`, \`/cc-view status\`, \`/cc-view tree\`, \`/cc-view diff\`, or asks to "show cclaw status", "show the flow tree", "diff flow state", or wants a snapshot without mutation.`;
321
+ case "ops":
322
+ return `Operations router for cclaw post-flow actions. Use when the user types \`/cc-ops\`, \`/cc-ops feature\`, \`/cc-ops tdd-log\`, \`/cc-ops retro\`, \`/cc-ops compound\`, \`/cc-ops archive\`, \`/cc-ops rewind\`, or asks to "archive the run", "run the retro", "compound knowledge", "rewind to an earlier stage", or manage feature worktrees.`;
323
+ default:
324
+ return `Generated cclaw skill for ${command}.`;
325
+ }
326
+ }
327
+ /**
328
+ * Skill body for codex-kind shims. Deliberately terse — the meat lives in
329
+ * `.cclaw/skills/` and `.cclaw/commands/`, and Codex's progressive-disclosure
330
+ * model loads skill bodies lazily, so we want a pointer plus the honest
331
+ * harness caveat, not a duplicated contract.
332
+ */
333
+ function codexSkillBody(command, skillFolder, commandFile) {
334
+ const slashToken = command === "cc" ? "/cc" : `/cc-${command}`;
335
+ const title = command === "cc" ? "cclaw /cc (Codex adapter)" : `cclaw ${slashToken} (Codex adapter)`;
336
+ const extraContractHeading = command === "cc"
337
+ ? "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
+ : "This skill is a utility entry point, not a flow stage. Do not mutate `.cclaw/state/flow-state.json` directly.";
339
+ return `# ${title}
340
+
341
+ 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.
347
+
348
+ ## Protocol
349
+
350
+ 1. Read \`.cclaw/state/flow-state.json\` first to know the active stage,
351
+ track, and run metadata.
352
+ 2. Load and follow \`.cclaw/skills/${skillFolder}/SKILL.md\` as the
353
+ authoritative skill — its gates, artifacts, and delegations are
354
+ canonical.
355
+ 3. Load \`.cclaw/commands/${commandFile}\` for the full command contract
356
+ (protocol, validation, post-state expectations).
357
+ 4. ${extraContractHeading}
358
+
359
+ ## Honest caveats
360
+
361
+ - Codex has no subagent dispatch primitive. Mandatory delegations
362
+ fall back to **role-switch** — announce the role, act in-session,
363
+ append a completed row with \`evidenceRefs\` to
364
+ \`.cclaw/state/delegation-log.json\`. Silent auto-waiver is disabled
365
+ (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.
370
+ `;
371
+ }
372
+ function codexSkillMarkdown(command, skillName, skillFolder, commandFile) {
373
+ const description = codexSkillDescription(command);
374
+ const frontmatter = [
375
+ "---",
376
+ `name: ${skillName}`,
377
+ `description: ${description}`,
378
+ "source: generated-by-cclaw",
379
+ "---",
380
+ ""
381
+ ].join("\n");
382
+ return `${frontmatter}${codexSkillBody(command, skillFolder, commandFile)}`;
383
+ }
384
+ async function writeCommandKindShims(commandDir, harness) {
385
+ await ensureDir(commandDir);
386
+ await writeFileSafe(path.join(commandDir, "cc.md"), utilityShimContent(harness, "cc", "flow-start", "start.md"));
387
+ for (const shim of UTILITY_SHIMS) {
388
+ await writeFileSafe(path.join(commandDir, shim.fileName), utilityShimContent(harness, shim.command, shim.skillFolder, shim.commandFile));
389
+ }
390
+ for (const legacy of LEGACY_HARNESS_SHIMS) {
391
+ const legacyPath = path.join(commandDir, legacy);
392
+ try {
393
+ await fs.unlink(legacyPath);
394
+ }
395
+ catch {
396
+ // fine — file may not exist (fresh install) or may be on read-only FS
397
+ }
398
+ }
399
+ }
400
+ async function writeSkillKindShims(commandDir) {
401
+ await ensureDir(commandDir);
402
+ await writeFileSafe(path.join(commandDir, ENTRY_SHIM_SKILL_NAME, "SKILL.md"), codexSkillMarkdown("cc", ENTRY_SHIM_SKILL_NAME, "flow-start", "start.md"));
403
+ for (const shim of UTILITY_SHIMS) {
404
+ await writeFileSafe(path.join(commandDir, shim.skillName, "SKILL.md"), codexSkillMarkdown(shim.command, shim.skillName, shim.skillFolder, shim.commandFile));
405
+ }
406
+ }
407
+ /**
408
+ * 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).
412
+ */
413
+ async function cleanupLegacyCodexSurfaces(projectRoot) {
414
+ const legacyCommandsDir = path.join(projectRoot, ".codex/commands");
415
+ try {
416
+ await fs.rm(legacyCommandsDir, { recursive: true, force: true });
417
+ }
418
+ catch {
419
+ // best-effort cleanup
420
+ }
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
427
+ }
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.
431
+ try {
432
+ const codexDir = path.join(projectRoot, ".codex");
433
+ const entries = await fs.readdir(codexDir);
434
+ if (entries.length === 0) {
435
+ await fs.rmdir(codexDir);
436
+ }
437
+ }
438
+ catch {
439
+ // directory absent or non-empty
440
+ }
441
+ }
269
442
  async function syncAgentFiles(projectRoot) {
270
443
  const agentsDir = path.join(projectRoot, RUNTIME_ROOT, "agents");
271
444
  await ensureDir(agentsDir);
@@ -274,25 +447,20 @@ async function syncAgentFiles(projectRoot) {
274
447
  }
275
448
  }
276
449
  export async function syncHarnessShims(projectRoot, harnesses) {
450
+ // Legacy codex cleanup is unconditional — even installs that never enabled
451
+ // codex but previously did will see stale `.codex/commands/*.md` and
452
+ // `.codex/hooks.json` get removed on upgrade.
453
+ await cleanupLegacyCodexSurfaces(projectRoot);
277
454
  for (const harness of harnesses) {
278
455
  const adapter = HARNESS_ADAPTERS[harness];
279
- if (!adapter) {
456
+ if (!adapter)
280
457
  continue;
281
- }
282
458
  const commandDir = path.join(projectRoot, adapter.commandDir);
283
- await ensureDir(commandDir);
284
- await writeFileSafe(path.join(commandDir, "cc.md"), utilityShimContent(harness, "cc", "flow-start", "start.md"));
285
- for (const shim of UTILITY_SHIMS) {
286
- await writeFileSafe(path.join(commandDir, shim.fileName), utilityShimContent(harness, shim.command, shim.skillFolder, shim.commandFile));
459
+ if (adapter.shimKind === "skill") {
460
+ await writeSkillKindShims(commandDir);
287
461
  }
288
- for (const legacy of LEGACY_HARNESS_SHIMS) {
289
- const legacyPath = path.join(commandDir, legacy);
290
- try {
291
- await fs.unlink(legacyPath);
292
- }
293
- catch {
294
- // fine — file may not exist (fresh install) or may be on read-only FS
295
- }
462
+ else {
463
+ await writeCommandKindShims(commandDir, harness);
296
464
  }
297
465
  }
298
466
  await syncAgentFiles(projectRoot);
@@ -26,9 +26,14 @@ export async function detectHarnesses(projectRoot) {
26
26
  if (await anyExists(opencodeHints)) {
27
27
  detected.push("opencode");
28
28
  }
29
+ // Codex CLI doesn't require a persistent per-project directory. We
30
+ // detect via `.agents/skills/` (the universal path Codex 0.89+ reads;
31
+ // Jan 2026) or the legacy `.codex/` marker left by pre-v0.39 cclaw.
32
+ // AGENTS.md is intentionally *not* a codex hint because every other
33
+ // harness in cclaw's list also reads AGENTS.md.
29
34
  const codexHints = [
30
- path.join(projectRoot, ".codex"),
31
- path.join(projectRoot, ".codex/hooks.json")
35
+ path.join(projectRoot, ".agents/skills"),
36
+ path.join(projectRoot, ".codex")
32
37
  ];
33
38
  if (await anyExists(codexHints)) {
34
39
  detected.push("codex");
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, codexHooksJson } from "./content/hooks.js";
26
+ import { sessionStartScript, stopCheckpointScript, preCompactScript, opencodePluginJs, claudeHooksJson, 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,11 +667,10 @@ async function writeHooks(projectRoot, config) {
667
667
  await ensureDir(cursorDir);
668
668
  await writeMergedHookJson(projectRoot, path.join(cursorDir, "hooks.json"), cursorHooksJson());
669
669
  }
670
- else if (harness === "codex") {
671
- const dir = path.join(projectRoot, ".codex");
672
- await ensureDir(dir);
673
- await writeMergedHookJson(projectRoot, path.join(dir, "hooks.json"), codexHooksJson());
674
- }
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.
675
674
  // OpenCode registration is auto-managed via opencode.json/opencode.jsonc.
676
675
  }
677
676
  }
@@ -906,10 +905,13 @@ async function writeCursorWorkflowRule(projectRoot, harnesses) {
906
905
  }
907
906
  async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
908
907
  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.
909
912
  const managedHookFiles = [
910
913
  { harness: "claude", hookPath: path.join(projectRoot, ".claude/hooks/hooks.json") },
911
- { harness: "cursor", hookPath: path.join(projectRoot, ".cursor/hooks.json") },
912
- { harness: "codex", hookPath: path.join(projectRoot, ".codex/hooks.json") }
914
+ { harness: "cursor", hookPath: path.join(projectRoot, ".cursor/hooks.json") }
913
915
  ];
914
916
  for (const entry of managedHookFiles) {
915
917
  if (enabled.has(entry.harness))
@@ -1075,6 +1077,12 @@ async function cleanLegacyArtifacts(projectRoot) {
1075
1077
  async function cleanStaleFiles(projectRoot) {
1076
1078
  const expectedShimFiles = new Set(harnessShimFileNames());
1077
1079
  for (const adapter of Object.values(HARNESS_ADAPTERS)) {
1080
+ // Skill-kind shims (Codex) live in per-skill directories, not flat
1081
+ // markdown files, so the regex-based stale sweep below would never
1082
+ // match them anyway. The legacy `.codex/commands/` cleanup happens in
1083
+ // `cleanupLegacyCodexSurfaces` inside syncHarnessShims().
1084
+ if (adapter.shimKind === "skill")
1085
+ continue;
1078
1086
  const commandDir = path.join(projectRoot, adapter.commandDir);
1079
1087
  if (!(await exists(commandDir)))
1080
1088
  continue;
@@ -1298,6 +1306,24 @@ export async function uninstallCclaw(projectRoot) {
1298
1306
  // directory not present
1299
1307
  }
1300
1308
  }
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.
1313
+ const codexSkillsRoot = path.join(projectRoot, ".agents/skills");
1314
+ try {
1315
+ const entries = await fs.readdir(codexSkillsRoot);
1316
+ for (const entry of entries) {
1317
+ if (/^cclaw-(?:cc)(?:-.*)?$/u.test(entry)) {
1318
+ await fs.rm(path.join(codexSkillsRoot, entry), { recursive: true, force: true });
1319
+ }
1320
+ }
1321
+ }
1322
+ catch {
1323
+ // directory not present
1324
+ }
1325
+ await removeIfEmpty(codexSkillsRoot);
1326
+ await removeIfEmpty(path.join(projectRoot, ".agents"));
1301
1327
  for (const pluginPath of [
1302
1328
  path.join(projectRoot, ".opencode/plugins/viby-plugin.mjs"),
1303
1329
  path.join(projectRoot, ".opencode/plugins/opencode-plugin.mjs"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.38.0",
3
+ "version": "0.39.0",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {