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 +16 -3
- package/dist/cli.js +9 -4
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +4 -3
- package/dist/content/harness-playbooks.js +56 -27
- package/dist/content/harnesses-doc.js +1 -1
- package/dist/content/hook-events.js +5 -6
- package/dist/content/hooks.js +5 -3
- package/dist/doctor.js +64 -33
- package/dist/harness-adapters.d.ts +25 -0
- package/dist/harness-adapters.js +184 -16
- package/dist/init-detect.js +7 -2
- package/dist/install.js +34 -8
- package/package.json +1 -1
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
|
-
- `.
|
|
131
|
-
-
|
|
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) |
|
|
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
|
-
|
|
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
|
-
|
|
163
|
-
|
|
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");
|
package/dist/constants.d.ts
CHANGED
|
@@ -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", ".
|
|
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
|
-
|
|
95
|
-
|
|
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
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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.**
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
3. **EvidenceRefs are mandatory.**
|
|
215
|
-
\`
|
|
216
|
-
|
|
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
|
-
##
|
|
244
|
+
## Invocation cheatsheet
|
|
230
245
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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\`
|
|
244
|
-
|
|
245
|
-
|
|
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\`: \`.
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
};
|
package/dist/content/hooks.js
CHANGED
|
@@ -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
|
|
1209
|
-
|
|
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
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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"
|
|
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
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
const
|
|
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: "
|
|
767
|
-
ok,
|
|
768
|
-
details:
|
|
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
|
|
771
|
-
const
|
|
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: "
|
|
781
|
-
ok:
|
|
782
|
-
details:
|
|
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;
|
package/dist/harness-adapters.js
CHANGED
|
@@ -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
|
-
|
|
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: "
|
|
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
|
-
|
|
284
|
-
|
|
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
|
-
|
|
289
|
-
|
|
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);
|
package/dist/init-detect.js
CHANGED
|
@@ -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, ".
|
|
31
|
-
path.join(projectRoot, ".codex
|
|
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
|
|
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
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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"),
|