cclaw-cli 0.39.1 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -15
- package/dist/cli.js +88 -4
- package/dist/codex-feature-flag.d.ts +58 -0
- package/dist/codex-feature-flag.js +193 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +7 -4
- package/dist/content/compound-command.js +4 -2
- package/dist/content/harness-playbooks.js +98 -31
- package/dist/content/harness-tool-refs.d.ts +1 -1
- package/dist/content/harness-tool-refs.js +38 -10
- package/dist/content/harnesses-doc.js +1 -1
- package/dist/content/hook-events.js +11 -5
- package/dist/content/hooks.js +2 -2
- package/dist/content/observe.d.ts +19 -0
- package/dist/content/observe.js +29 -13
- package/dist/content/protocols.js +12 -4
- package/dist/content/retro-command.js +8 -4
- package/dist/content/stages/design.js +1 -1
- package/dist/content/stages/review.js +2 -2
- package/dist/content/stages/scope.js +1 -1
- package/dist/content/stages/ship.js +1 -1
- package/dist/content/start-command.js +1 -1
- package/dist/content/subagents.js +2 -2
- package/dist/doctor.js +86 -21
- package/dist/harness-adapters.d.ts +17 -1
- package/dist/harness-adapters.js +105 -43
- package/dist/install.js +46 -17
- package/package.json +1 -1
package/dist/doctor.js
CHANGED
|
@@ -20,6 +20,7 @@ import { reconcileAndWriteCurrentStageGateCatalog, verifyCompletedStagesGateClos
|
|
|
20
20
|
import { parseTddCycleLog, validateTddCycleOrder } from "./tdd-cycle.js";
|
|
21
21
|
import { stageSkillFolder } from "./content/skills.js";
|
|
22
22
|
import { doctorCheckMetadata } from "./doctor-registry.js";
|
|
23
|
+
import { classifyCodexHooksFlag, codexConfigPath, readCodexConfig } from "./codex-feature-flag.js";
|
|
23
24
|
import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LEGACY_LANGUAGE_RULE_PACK_FOLDERS, UTILITY_SKILL_FOLDERS } from "./content/utility-skills.js";
|
|
24
25
|
import { CONTEXT_MODES, DEFAULT_CONTEXT_MODE } from "./content/contexts.js";
|
|
25
26
|
import { DOCTOR_REFERENCE_MARKDOWN } from "./content/doctor-references.js";
|
|
@@ -635,16 +636,18 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
635
636
|
});
|
|
636
637
|
}
|
|
637
638
|
}
|
|
638
|
-
// Hook JSON files per harness.
|
|
639
|
-
//
|
|
640
|
-
//
|
|
639
|
+
// Hook JSON files per harness. OpenCode ships hooks through its plugin
|
|
640
|
+
// system (covered below). Codex joined the managed list in v0.40.0 — Codex
|
|
641
|
+
// CLI ≥ v0.114 consumes `.codex/hooks.json` behind the `codex_hooks`
|
|
642
|
+
// feature flag.
|
|
641
643
|
const hookPaths = {
|
|
642
644
|
claude: ".claude/hooks/hooks.json",
|
|
643
|
-
cursor: ".cursor/hooks.json"
|
|
645
|
+
cursor: ".cursor/hooks.json",
|
|
646
|
+
codex: ".codex/hooks.json"
|
|
644
647
|
};
|
|
645
648
|
for (const harness of configuredHarnesses) {
|
|
646
649
|
const hp = hookPaths[harness];
|
|
647
|
-
if (!hp && harness !== "opencode"
|
|
650
|
+
if (!hp && harness !== "opencode") {
|
|
648
651
|
checks.push({
|
|
649
652
|
name: `hook:json:${harness}`,
|
|
650
653
|
ok: false,
|
|
@@ -661,7 +664,7 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
661
664
|
ok: hookOk,
|
|
662
665
|
details: fullPath
|
|
663
666
|
});
|
|
664
|
-
if (harness === "claude" || harness === "cursor") {
|
|
667
|
+
if (harness === "claude" || harness === "cursor" || harness === "codex") {
|
|
665
668
|
const schema = validateHookDocument(harness, parsed);
|
|
666
669
|
checks.push({
|
|
667
670
|
name: `hook:schema:${harness}`,
|
|
@@ -762,10 +765,11 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
762
765
|
});
|
|
763
766
|
}
|
|
764
767
|
if (configuredHarnesses.includes("codex")) {
|
|
765
|
-
// Codex CLI has no
|
|
766
|
-
//
|
|
767
|
-
// skills under `.agents/skills/
|
|
768
|
-
//
|
|
768
|
+
// Codex CLI has no custom slash-command discovery (`.codex/commands/*`
|
|
769
|
+
// was never read, even historically). cclaw ships codex entry points
|
|
770
|
+
// as skills under `.agents/skills/cc*/SKILL.md`; Codex v0.114+ also
|
|
771
|
+
// supports lifecycle hooks at `.codex/hooks.json` (gated by the
|
|
772
|
+
// `codex_hooks` feature flag in `~/.codex/config.toml`).
|
|
769
773
|
const skillsRoot = path.join(projectRoot, ".agents/skills");
|
|
770
774
|
for (const skillName of harnessShimSkillNames()) {
|
|
771
775
|
const skillPath = path.join(skillsRoot, skillName, "SKILL.md");
|
|
@@ -791,26 +795,87 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
791
795
|
: `${skillPath} absent; cannot validate frontmatter`
|
|
792
796
|
});
|
|
793
797
|
}
|
|
794
|
-
//
|
|
795
|
-
//
|
|
796
|
-
//
|
|
798
|
+
// Hook wiring: the generated `.codex/hooks.json` must reference every
|
|
799
|
+
// runtime script cclaw needs. Separate from the schema check above;
|
|
800
|
+
// schema covers structure, this check covers semantic wiring.
|
|
801
|
+
const codexHooksFile = path.join(projectRoot, ".codex/hooks.json");
|
|
802
|
+
const codexDoc = await readHookDocument(codexHooksFile);
|
|
803
|
+
const codexHooks = toObject(codexDoc?.hooks) ?? {};
|
|
804
|
+
const codexSessionCmds = collectHookCommands(codexHooks.SessionStart);
|
|
805
|
+
const codexPreCmds = collectHookCommands(codexHooks.PreToolUse);
|
|
806
|
+
const codexPostCmds = collectHookCommands(codexHooks.PostToolUse);
|
|
807
|
+
const codexStopCmds = collectHookCommands(codexHooks.Stop);
|
|
808
|
+
const codexWiringOk = codexSessionCmds.some((cmd) => cmd.includes("session-start.sh")) &&
|
|
809
|
+
codexPreCmds.some((cmd) => cmd.includes("prompt-guard.sh")) &&
|
|
810
|
+
codexPreCmds.some((cmd) => cmd.includes("workflow-guard.sh")) &&
|
|
811
|
+
codexPostCmds.some((cmd) => cmd.includes("context-monitor.sh")) &&
|
|
812
|
+
codexStopCmds.some((cmd) => cmd.includes("stop-checkpoint.sh"));
|
|
813
|
+
checks.push({
|
|
814
|
+
name: "hook:wiring:codex",
|
|
815
|
+
ok: codexWiringOk,
|
|
816
|
+
details: `${codexHooksFile} must wire session-start/prompt-guard/workflow-guard/context-monitor/stop-checkpoint (PreToolUse/PostToolUse run Bash-only in Codex v0.114+)`
|
|
817
|
+
});
|
|
818
|
+
// Feature flag warning: Codex ignores `.codex/hooks.json` unless the
|
|
819
|
+
// user has `[features] codex_hooks = true` in `~/.codex/config.toml`.
|
|
820
|
+
// Advisory warning — not a hard failure, because the skills still
|
|
821
|
+
// work without the flag.
|
|
822
|
+
const codexConfig = codexConfigPath();
|
|
823
|
+
let featureFlagNote = "";
|
|
824
|
+
try {
|
|
825
|
+
const content = await readCodexConfig(codexConfig);
|
|
826
|
+
const state = classifyCodexHooksFlag(content);
|
|
827
|
+
featureFlagNote =
|
|
828
|
+
state === "enabled"
|
|
829
|
+
? `codex_hooks feature flag is enabled in ${codexConfig}`
|
|
830
|
+
: state === "missing-file"
|
|
831
|
+
? `warning: ${codexConfig} does not exist; .codex/hooks.json will be ignored until you create it with \`[features]\\ncodex_hooks = true\\n\`.`
|
|
832
|
+
: state === "missing-section"
|
|
833
|
+
? `warning: ${codexConfig} has no [features] section; add \`[features]\\ncodex_hooks = true\\n\` to enable cclaw hooks.`
|
|
834
|
+
: state === "missing-key"
|
|
835
|
+
? `warning: ${codexConfig} is missing the codex_hooks key under [features]. Add \`codex_hooks = true\` to enable cclaw hooks.`
|
|
836
|
+
: `warning: ${codexConfig} sets codex_hooks to a non-true value; set \`codex_hooks = true\` under [features] to enable cclaw hooks.`;
|
|
837
|
+
}
|
|
838
|
+
catch (err) {
|
|
839
|
+
featureFlagNote = `warning: could not read ${codexConfig}: ${err instanceof Error ? err.message : String(err)}`;
|
|
840
|
+
}
|
|
841
|
+
checks.push({
|
|
842
|
+
name: "warning:codex:feature_flag",
|
|
843
|
+
ok: true,
|
|
844
|
+
details: featureFlagNote
|
|
845
|
+
});
|
|
846
|
+
// Legacy `.codex/commands/*` must not linger from older cclaw installs.
|
|
847
|
+
// (The `.codex/hooks.json` path is now managed and is validated above,
|
|
848
|
+
// so there is no longer a legacy_hooks_json warning.)
|
|
797
849
|
const legacyCommandsDir = path.join(projectRoot, ".codex/commands");
|
|
798
850
|
const legacyCommandsPresent = await exists(legacyCommandsDir);
|
|
799
851
|
checks.push({
|
|
800
852
|
name: "warning:codex:legacy_commands_dir",
|
|
801
853
|
ok: true,
|
|
802
854
|
details: legacyCommandsPresent
|
|
803
|
-
? `warning: ${legacyCommandsDir} still present; Codex never
|
|
855
|
+
? `warning: ${legacyCommandsDir} still present; Codex never consumed this directory — run \`cclaw sync\` to remove it.`
|
|
804
856
|
: `no legacy ${legacyCommandsDir} detected`
|
|
805
857
|
});
|
|
806
|
-
|
|
807
|
-
|
|
858
|
+
// Legacy v0.39.x skill layout under `.agents/skills/cclaw-cc*/`
|
|
859
|
+
// must have been removed — cclaw sync deletes these automatically,
|
|
860
|
+
// but flag leftovers so users notice an upgrade issue.
|
|
861
|
+
const legacyCodexSkills = [];
|
|
862
|
+
try {
|
|
863
|
+
const entries = await fs.readdir(skillsRoot);
|
|
864
|
+
for (const entry of entries) {
|
|
865
|
+
if (/^cclaw-cc(?:-.*)?$/u.test(entry)) {
|
|
866
|
+
legacyCodexSkills.push(entry);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
catch {
|
|
871
|
+
// skills root absent; nothing to warn about
|
|
872
|
+
}
|
|
808
873
|
checks.push({
|
|
809
|
-
name: "warning:codex:
|
|
810
|
-
ok:
|
|
811
|
-
details:
|
|
812
|
-
? `
|
|
813
|
-
: `
|
|
874
|
+
name: "warning:codex:legacy_cclaw_cc_skills",
|
|
875
|
+
ok: legacyCodexSkills.length === 0,
|
|
876
|
+
details: legacyCodexSkills.length === 0
|
|
877
|
+
? `no legacy cclaw-cc* skill folders detected under .agents/skills/`
|
|
878
|
+
: `warning: legacy skill folders from cclaw v0.39.x present (${legacyCodexSkills.join(", ")}); run \`cclaw sync\` to remove them.`
|
|
814
879
|
});
|
|
815
880
|
}
|
|
816
881
|
if (configuredHarnesses.includes("opencode")) {
|
|
@@ -58,7 +58,23 @@ export interface HarnessAdapter {
|
|
|
58
58
|
*/
|
|
59
59
|
nativeSubagentDispatch: "full" | "generic" | "partial" | "none";
|
|
60
60
|
hookSurface: "full" | "plugin" | "limited" | "none";
|
|
61
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Structured-ask primitive exposed by the harness.
|
|
63
|
+
*
|
|
64
|
+
* - `AskUserQuestion` — Claude Code native tool (≤5 options × multi-question).
|
|
65
|
+
* - `AskQuestion` — Cursor native tool (≥2 options, multi-question, `allow_multiple`).
|
|
66
|
+
* - `question` — OpenCode native tool (header + options + "type custom"
|
|
67
|
+
* fallback); **gated**: requires `permission.question: "allow"` in
|
|
68
|
+
* `opencode.json`, and for ACP clients additionally needs
|
|
69
|
+
* `OPENCODE_ENABLE_QUESTION_TOOL=1`.
|
|
70
|
+
* - `request_user_input` — Codex CLI tool (1-3 short questions); experimental
|
|
71
|
+
* and primarily surfaced inside Plan / Collaboration mode templates
|
|
72
|
+
* (`codex-rs/collaboration-mode-templates`). Available to agents running
|
|
73
|
+
* inside Codex but may be hidden on very old builds.
|
|
74
|
+
* - `plain-text` — fallback only; used when no native primitive is
|
|
75
|
+
* available (no shipping harness uses this in v0.41.0).
|
|
76
|
+
*/
|
|
77
|
+
structuredAsk: "AskUserQuestion" | "AskQuestion" | "question" | "request_user_input" | "plain-text";
|
|
62
78
|
/**
|
|
63
79
|
* Declared fallback pattern used when the harness cannot satisfy a
|
|
64
80
|
* mandatory delegation natively. Drives `checkMandatoryDelegations`
|
package/dist/harness-adapters.js
CHANGED
|
@@ -14,35 +14,48 @@ const RUNTIME_AGENTS_BLOCK_GLOBAL_PATTERN = new RegExp(RUNTIME_AGENTS_BLOCK_SOUR
|
|
|
14
14
|
const UTILITY_SHIMS = [
|
|
15
15
|
{
|
|
16
16
|
fileName: "cc-next.md",
|
|
17
|
-
skillName: "
|
|
17
|
+
skillName: "cc-next",
|
|
18
18
|
command: "next",
|
|
19
19
|
skillFolder: "flow-next-step",
|
|
20
20
|
commandFile: "next.md"
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
fileName: "cc-ideate.md",
|
|
24
|
-
skillName: "
|
|
24
|
+
skillName: "cc-ideate",
|
|
25
25
|
command: "ideate",
|
|
26
26
|
skillFolder: "flow-ideate",
|
|
27
27
|
commandFile: "ideate.md"
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
fileName: "cc-view.md",
|
|
31
|
-
skillName: "
|
|
31
|
+
skillName: "cc-view",
|
|
32
32
|
command: "view",
|
|
33
33
|
skillFolder: "flow-view",
|
|
34
34
|
commandFile: "view.md"
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
fileName: "cc-ops.md",
|
|
38
|
-
skillName: "
|
|
38
|
+
skillName: "cc-ops",
|
|
39
39
|
command: "ops",
|
|
40
40
|
skillFolder: "flow-ops",
|
|
41
41
|
commandFile: "ops.md"
|
|
42
42
|
}
|
|
43
43
|
];
|
|
44
44
|
/** Skill-kind shim name for the root `/cc` entry point. */
|
|
45
|
-
const ENTRY_SHIM_SKILL_NAME = "
|
|
45
|
+
const ENTRY_SHIM_SKILL_NAME = "cc";
|
|
46
|
+
/**
|
|
47
|
+
* Skill directory names that v0.39.0 / v0.39.1 installed under
|
|
48
|
+
* `.agents/skills/` before the rename. We delete these on every sync so
|
|
49
|
+
* upgrades from those versions do not leave orphaned `cclaw-cc*`
|
|
50
|
+
* folders that would double-register in Codex's skill listing.
|
|
51
|
+
*/
|
|
52
|
+
const LEGACY_CODEX_SKILL_NAMES = [
|
|
53
|
+
"cclaw-cc",
|
|
54
|
+
"cclaw-cc-next",
|
|
55
|
+
"cclaw-cc-view",
|
|
56
|
+
"cclaw-cc-ops",
|
|
57
|
+
"cclaw-cc-ideate"
|
|
58
|
+
];
|
|
46
59
|
/**
|
|
47
60
|
* Shims that older cclaw versions installed as top-level slash commands but
|
|
48
61
|
* which we now treat as internal (skill-only, invoked by the agent, never
|
|
@@ -91,24 +104,44 @@ export const HARNESS_ADAPTERS = {
|
|
|
91
104
|
capabilities: {
|
|
92
105
|
nativeSubagentDispatch: "partial",
|
|
93
106
|
hookSurface: "plugin",
|
|
94
|
-
|
|
107
|
+
// OpenCode exposes a native `question` tool (header + options +
|
|
108
|
+
// custom-answer fallback, multi-question navigation). It is
|
|
109
|
+
// permission-gated — `opencode.json` must set
|
|
110
|
+
// `permission.question: "allow"` and ACP clients must export
|
|
111
|
+
// `OPENCODE_ENABLE_QUESTION_TOOL=1`. cclaw surfaces the tool name
|
|
112
|
+
// in the Decision Protocol and in the OpenCode playbook; skills
|
|
113
|
+
// fall back to the shared plain-text lettered list when the tool
|
|
114
|
+
// is denied or unavailable.
|
|
115
|
+
structuredAsk: "question",
|
|
95
116
|
subagentFallback: "role-switch"
|
|
96
117
|
}
|
|
97
118
|
},
|
|
98
119
|
codex: {
|
|
99
120
|
id: "codex",
|
|
100
121
|
// Codex CLI reads skills from the universal `.agents/skills/` path
|
|
101
|
-
// (OpenAI Codex 0.89, Jan 2026
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
122
|
+
// (OpenAI Codex 0.89, Jan 2026). It does NOT have a native
|
|
123
|
+
// `.codex/commands/*` slash-command discovery — cclaw installs
|
|
124
|
+
// its entry points as skills here. Since v0.114 (Mar 2026) Codex
|
|
125
|
+
// also exposes lifecycle hooks via `.codex/hooks.json`, behind
|
|
126
|
+
// the `[features] codex_hooks = true` feature flag in
|
|
127
|
+
// `~/.codex/config.toml`. cclaw writes that file on sync and
|
|
128
|
+
// `hookSurface: "limited"` records the reality: SessionStart /
|
|
129
|
+
// UserPromptSubmit / Stop fire for every turn, but PreToolUse /
|
|
130
|
+
// PostToolUse only intercept the `Bash` tool.
|
|
106
131
|
commandDir: ".agents/skills",
|
|
107
132
|
shimKind: "skill",
|
|
108
133
|
capabilities: {
|
|
109
134
|
nativeSubagentDispatch: "none",
|
|
110
|
-
hookSurface: "
|
|
111
|
-
|
|
135
|
+
hookSurface: "limited",
|
|
136
|
+
// Codex CLI exposes `request_user_input` — an experimental tool
|
|
137
|
+
// that asks 1-3 short questions and returns the user's answers.
|
|
138
|
+
// It is the primitive the built-in Plan / Collaboration mode
|
|
139
|
+
// templates use (see `codex-rs/collaboration-mode-templates`).
|
|
140
|
+
// Agents running inside Codex can call it directly; cclaw wires
|
|
141
|
+
// it into the Decision Protocol and the Codex playbook. The
|
|
142
|
+
// shared plain-text lettered list is the documented fallback
|
|
143
|
+
// when the tool is unavailable.
|
|
144
|
+
structuredAsk: "request_user_input",
|
|
112
145
|
subagentFallback: "role-switch"
|
|
113
146
|
}
|
|
114
147
|
}
|
|
@@ -122,6 +155,7 @@ export function harnessTier(harnessId) {
|
|
|
122
155
|
}
|
|
123
156
|
if (capabilities.hookSurface === "full" ||
|
|
124
157
|
capabilities.hookSurface === "plugin" ||
|
|
158
|
+
capabilities.hookSurface === "limited" ||
|
|
125
159
|
capabilities.nativeSubagentDispatch === "generic" ||
|
|
126
160
|
capabilities.nativeSubagentDispatch === "partial") {
|
|
127
161
|
return "tier2";
|
|
@@ -209,26 +243,33 @@ If the same approach fails three times in a row (same command, same finding, sam
|
|
|
209
243
|
### Detail Level
|
|
210
244
|
|
|
211
245
|
- This managed AGENTS block is intentionally minimal for cross-project use.
|
|
212
|
-
- Harness coverage is tiered: Tier1 (claude), Tier2 (cursor/opencode/codex), Tier3 (fallback/manual-only).
|
|
246
|
+
- Harness coverage is tiered: Tier1 (claude), Tier2 (cursor/opencode/codex — codex has Bash-only tool hooks), Tier3 (fallback/manual-only).
|
|
213
247
|
- Detailed operating procedures live in \`.cclaw/skills/using-cclaw/SKILL.md\`.
|
|
214
248
|
- Preamble budget and cooldown rules live in \`.cclaw/references/protocols/ethos.md\`.
|
|
215
249
|
- Subagent orchestration patterns: \`.cclaw/skills/subagent-dev/SKILL.md\` and \`.cclaw/skills/parallel-dispatch/SKILL.md\`.
|
|
216
250
|
|
|
217
251
|
### Codex users
|
|
218
252
|
|
|
219
|
-
OpenAI Codex CLI has **no native \`/cc\` slash command**
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
253
|
+
OpenAI Codex CLI has **no native \`/cc\` slash command** (custom prompts
|
|
254
|
+
were deprecated in v0.89, Jan 2026). The \`/cc\`, \`/cc-next\`,
|
|
255
|
+
\`/cc-ideate\`, \`/cc-view\`, \`/cc-ops\` tokens above describe intent — in
|
|
256
|
+
Codex they map onto skills cclaw installs at
|
|
257
|
+
\`.agents/skills/cc*/SKILL.md\`. Activate one of two ways:
|
|
223
258
|
|
|
224
|
-
- Type \`/use
|
|
259
|
+
- Type \`/use cc\` (or \`cc-next\`, etc.) at Codex's prompt.
|
|
225
260
|
- Type \`/cc …\` as plain text — Codex matches the skill \`description\`
|
|
226
261
|
frontmatter (which spells out the token verbatim) and loads the right
|
|
227
262
|
skill body automatically.
|
|
228
263
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
\`.cclaw/
|
|
264
|
+
Codex CLI v0.114+ (Mar 2026) **does** expose lifecycle hooks via
|
|
265
|
+
\`.codex/hooks.json\`, gated by the \`[features] codex_hooks = true\` flag
|
|
266
|
+
in \`~/.codex/config.toml\`. cclaw generates \`.codex/hooks.json\` on
|
|
267
|
+
sync; if the feature flag is off, hooks are inert and cclaw's
|
|
268
|
+
session-start rehydration simply does not fire. Run \`cclaw doctor\` to
|
|
269
|
+
see if the flag is missing. \`.codex/commands/*\` is still unused by
|
|
270
|
+
Codex CLI and is removed on every sync. See
|
|
271
|
+
\`.cclaw/references/harnesses/codex-playbook.md\` for the hook coverage
|
|
272
|
+
matrix (Bash-only \`PreToolUse\`/\`PostToolUse\`; other events are full).
|
|
232
273
|
${CCLAW_MARKER_END}`;
|
|
233
274
|
}
|
|
234
275
|
/** Removes the cclaw AGENTS.md block. */
|
|
@@ -336,14 +377,22 @@ function codexSkillBody(command, skillFolder, commandFile) {
|
|
|
336
377
|
const extraContractHeading = command === "cc"
|
|
337
378
|
? "If you have not already loaded the cclaw meta-skill this session, also load `.cclaw/skills/using-cclaw/SKILL.md` — it is the routing brain for stage/utility selection."
|
|
338
379
|
: "This skill is a utility entry point, not a flow stage. Do not mutate `.cclaw/state/flow-state.json` directly.";
|
|
380
|
+
const skillSlug = command === "cc" ? "cc" : `cc-${command}`;
|
|
339
381
|
return `# ${title}
|
|
340
382
|
|
|
341
383
|
You are running inside the OpenAI Codex harness. Codex has **no native
|
|
342
|
-
\`${slashToken}\` slash command
|
|
343
|
-
ships its entry points as skills
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
384
|
+
\`${slashToken}\` slash command** — custom prompts were deprecated in
|
|
385
|
+
Codex CLI v0.89 (Jan 2026). cclaw ships its entry points as skills
|
|
386
|
+
under \`.agents/skills/${skillSlug}/\` so the user can either:
|
|
387
|
+
|
|
388
|
+
- Type \`/use ${skillSlug}\` at the Codex prompt, or
|
|
389
|
+
- Type \`${slashToken} …\` (or describe the intent in English) — Codex's
|
|
390
|
+
skill matcher picks this skill up via the description frontmatter.
|
|
391
|
+
|
|
392
|
+
Lifecycle hooks **are** available in Codex CLI v0.114+ (behind the
|
|
393
|
+
\`[features] codex_hooks = true\` flag in \`~/.codex/config.toml\`) and
|
|
394
|
+
cclaw installs a matching \`.codex/hooks.json\` — see the playbook for
|
|
395
|
+
what the hook surface does and does not cover.
|
|
347
396
|
|
|
348
397
|
## Protocol
|
|
349
398
|
|
|
@@ -363,10 +412,11 @@ follow the steps below.
|
|
|
363
412
|
append a completed row with \`evidenceRefs\` to
|
|
364
413
|
\`.cclaw/state/delegation-log.json\`. Silent auto-waiver is disabled
|
|
365
414
|
(v0.33+).
|
|
366
|
-
- Codex
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
for
|
|
415
|
+
- Codex's \`PreToolUse\` / \`PostToolUse\` hooks currently only intercept
|
|
416
|
+
the \`Bash\` tool. \`Write\`, \`Edit\`, \`WebSearch\`, and MCP tool calls
|
|
417
|
+
are **not** gated by hooks — read
|
|
418
|
+
\`.cclaw/references/harnesses/codex-playbook.md\` for what cclaw
|
|
419
|
+
substitutes with in-turn agent steps for those call classes.
|
|
370
420
|
`;
|
|
371
421
|
}
|
|
372
422
|
function codexSkillMarkdown(command, skillName, skillFolder, commandFile) {
|
|
@@ -406,9 +456,16 @@ async function writeSkillKindShims(commandDir) {
|
|
|
406
456
|
}
|
|
407
457
|
/**
|
|
408
458
|
* Legacy codex surfaces cclaw wrote before v0.39.0 that Codex CLI never
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
459
|
+
* consumed (`.codex/commands/*.md` had no discovery primitive). We keep
|
|
460
|
+
* removing `.codex/commands/` on every sync so upgrades from those
|
|
461
|
+
* installs leave a clean slate, but as of v0.40.0 we DO write
|
|
462
|
+
* `.codex/hooks.json` again — Codex CLI grew a real hooks API in
|
|
463
|
+
* v0.114.0 (Mar 2026), and that file is the current, supported target.
|
|
464
|
+
*
|
|
465
|
+
* This function also removes skill folders named after the old
|
|
466
|
+
* `cclaw-cc*` scheme (v0.39.0 / v0.39.1) now that cclaw installs them
|
|
467
|
+
* as plain `cc*`. Leaving them around would make Codex list two skills
|
|
468
|
+
* for the same entry point.
|
|
412
469
|
*/
|
|
413
470
|
async function cleanupLegacyCodexSurfaces(projectRoot) {
|
|
414
471
|
const legacyCommandsDir = path.join(projectRoot, ".codex/commands");
|
|
@@ -418,16 +475,21 @@ async function cleanupLegacyCodexSurfaces(projectRoot) {
|
|
|
418
475
|
catch {
|
|
419
476
|
// best-effort cleanup
|
|
420
477
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
478
|
+
// Remove the old `cclaw-cc*` skill folders if they exist from a
|
|
479
|
+
// previous cclaw install. Idempotent; best-effort.
|
|
480
|
+
const legacySkillsRoot = path.join(projectRoot, ".agents/skills");
|
|
481
|
+
for (const name of LEGACY_CODEX_SKILL_NAMES) {
|
|
482
|
+
const folder = path.join(legacySkillsRoot, name);
|
|
483
|
+
try {
|
|
484
|
+
await fs.rm(folder, { recursive: true, force: true });
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
// best-effort
|
|
488
|
+
}
|
|
427
489
|
}
|
|
428
|
-
// If `.codex/` is now empty we drop it
|
|
429
|
-
//
|
|
430
|
-
//
|
|
490
|
+
// If `.codex/` is now empty we drop it — happens when neither hooks
|
|
491
|
+
// are enabled nor the user has their own state there. Otherwise we
|
|
492
|
+
// leave the directory alone.
|
|
431
493
|
try {
|
|
432
494
|
const codexDir = path.join(projectRoot, ".codex");
|
|
433
495
|
const entries = await fs.readdir(codexDir);
|
package/dist/install.js
CHANGED
|
@@ -23,7 +23,7 @@ import { archiveCommandContract, archiveCommandSkillMarkdown } from "./content/a
|
|
|
23
23
|
import { rewindCommandContract, rewindCommandSkillMarkdown } from "./content/rewind-command.js";
|
|
24
24
|
import { subagentDrivenDevSkill, parallelAgentsSkill } from "./content/subagents.js";
|
|
25
25
|
import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
|
|
26
|
-
import { sessionStartScript, stopCheckpointScript, preCompactScript, opencodePluginJs, claudeHooksJson, cursorHooksJson } from "./content/hooks.js";
|
|
26
|
+
import { sessionStartScript, stopCheckpointScript, preCompactScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
|
|
27
27
|
import { contextMonitorScript, promptGuardScript, workflowGuardScript } from "./content/observe.js";
|
|
28
28
|
import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
|
|
29
29
|
import { decisionProtocolMarkdown, completionProtocolMarkdown, ethosProtocolMarkdown } from "./content/protocols.js";
|
|
@@ -667,10 +667,18 @@ async function writeHooks(projectRoot, config) {
|
|
|
667
667
|
await ensureDir(cursorDir);
|
|
668
668
|
await writeMergedHookJson(projectRoot, path.join(cursorDir, "hooks.json"), cursorHooksJson());
|
|
669
669
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
670
|
+
else if (harness === "codex") {
|
|
671
|
+
// Codex CLI ≥ v0.114 (Mar 2026) supports lifecycle hooks at
|
|
672
|
+
// `.codex/hooks.json`, gated behind the `[features] codex_hooks = true`
|
|
673
|
+
// flag in `~/.codex/config.toml`. cclaw always writes the file so
|
|
674
|
+
// the moment the flag flips on, the cclaw hooks start firing. See
|
|
675
|
+
// `codexHooksJsonWithObservation` for the Bash-only caveat on
|
|
676
|
+
// PreToolUse/PostToolUse. `cclaw doctor` warns if the feature flag
|
|
677
|
+
// is not set.
|
|
678
|
+
const codexDir = path.join(projectRoot, ".codex");
|
|
679
|
+
await ensureDir(codexDir);
|
|
680
|
+
await writeMergedHookJson(projectRoot, path.join(codexDir, "hooks.json"), codexHooksJson());
|
|
681
|
+
}
|
|
674
682
|
// OpenCode registration is auto-managed via opencode.json/opencode.jsonc.
|
|
675
683
|
}
|
|
676
684
|
}
|
|
@@ -905,13 +913,14 @@ async function writeCursorWorkflowRule(projectRoot, harnesses) {
|
|
|
905
913
|
}
|
|
906
914
|
async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
|
|
907
915
|
const enabled = new Set(harnesses);
|
|
908
|
-
//
|
|
909
|
-
//
|
|
910
|
-
//
|
|
911
|
-
//
|
|
916
|
+
// v0.40.0: `.codex/hooks.json` is back on the managed list now that
|
|
917
|
+
// Codex CLI actually consumes it (v0.114+, Mar 2026). Legacy
|
|
918
|
+
// `.codex/commands/` cleanup still happens unconditionally from
|
|
919
|
+
// `cleanupLegacyCodexSurfaces` inside `syncHarnessShims`.
|
|
912
920
|
const managedHookFiles = [
|
|
913
921
|
{ harness: "claude", hookPath: path.join(projectRoot, ".claude/hooks/hooks.json") },
|
|
914
|
-
{ harness: "cursor", hookPath: path.join(projectRoot, ".cursor/hooks.json") }
|
|
922
|
+
{ harness: "cursor", hookPath: path.join(projectRoot, ".cursor/hooks.json") },
|
|
923
|
+
{ harness: "codex", hookPath: path.join(projectRoot, ".codex/hooks.json") }
|
|
915
924
|
];
|
|
916
925
|
for (const entry of managedHookFiles) {
|
|
917
926
|
if (enabled.has(entry.harness))
|
|
@@ -975,8 +984,24 @@ async function writeHarnessGapsState(projectRoot, harnesses) {
|
|
|
975
984
|
remediation.push(`subagent dispatch → record explicit harness_limitation waiver; no parity path available`);
|
|
976
985
|
break;
|
|
977
986
|
}
|
|
978
|
-
|
|
979
|
-
|
|
987
|
+
// Per-harness structuredAsk remediation: record either the fallback
|
|
988
|
+
// requirement (plain-text) or the gating / experimental status of the
|
|
989
|
+
// native primitive so `cclaw doctor` and harness-gaps.json stay
|
|
990
|
+
// honest about *why* a primitive might silently not fire.
|
|
991
|
+
switch (capabilities.structuredAsk) {
|
|
992
|
+
case "plain-text":
|
|
993
|
+
remediation.push("structured ask → fall back to a numbered plain-text list; first option is default");
|
|
994
|
+
break;
|
|
995
|
+
case "question":
|
|
996
|
+
remediation.push(`structured ask → OpenCode \`question\` tool; enable with \`permission.question: "allow"\` in \`opencode.json\` (ACP clients additionally need \`OPENCODE_ENABLE_QUESTION_TOOL=1\`). Fallback: shared plain-text lettered list.`);
|
|
997
|
+
break;
|
|
998
|
+
case "request_user_input":
|
|
999
|
+
remediation.push("structured ask → Codex `request_user_input` tool (experimental; surfaced in Plan / Collaboration mode). Fallback: shared plain-text lettered list when the tool is hidden.");
|
|
1000
|
+
break;
|
|
1001
|
+
case "AskUserQuestion":
|
|
1002
|
+
case "AskQuestion":
|
|
1003
|
+
// Native first-class ask — no remediation required.
|
|
1004
|
+
break;
|
|
980
1005
|
}
|
|
981
1006
|
for (const event of missingHookEvents) {
|
|
982
1007
|
remediation.push(`hook event ${event} → schedule the corresponding script manually or accept reduced observability`);
|
|
@@ -1306,15 +1331,19 @@ export async function uninstallCclaw(projectRoot) {
|
|
|
1306
1331
|
// directory not present
|
|
1307
1332
|
}
|
|
1308
1333
|
}
|
|
1309
|
-
//
|
|
1310
|
-
//
|
|
1311
|
-
//
|
|
1312
|
-
// `.agents/skills
|
|
1334
|
+
// Codex shim location history:
|
|
1335
|
+
// - < v0.39.0: `.codex/commands/cc*.md` (never consumed by Codex CLI)
|
|
1336
|
+
// - v0.39.0 / v0.39.1: `.agents/skills/cclaw-cc*/SKILL.md`
|
|
1337
|
+
// - ≥ v0.40.0: `.agents/skills/cc*/SKILL.md` (matches Codex's `/use cc`
|
|
1338
|
+
// prompt verbatim)
|
|
1339
|
+
// Remove all three legacy layouts on uninstall so orphans can't linger.
|
|
1340
|
+
// We only touch cclaw-owned folder names — other tools share
|
|
1341
|
+
// `.agents/skills/` with us.
|
|
1313
1342
|
const codexSkillsRoot = path.join(projectRoot, ".agents/skills");
|
|
1314
1343
|
try {
|
|
1315
1344
|
const entries = await fs.readdir(codexSkillsRoot);
|
|
1316
1345
|
for (const entry of entries) {
|
|
1317
|
-
if (/^cclaw-(?:
|
|
1346
|
+
if (/^(?:cclaw-)?cc(?:-(?:next|view|ops|ideate))?$/u.test(entry)) {
|
|
1318
1347
|
await fs.rm(path.join(codexSkillsRoot, entry), { recursive: true, force: true });
|
|
1319
1348
|
}
|
|
1320
1349
|
}
|