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/README.md
CHANGED
|
@@ -127,9 +127,12 @@ Plus harness-specific shims:
|
|
|
127
127
|
- `.claude/commands/cc*.md` + `.claude/hooks/hooks.json`
|
|
128
128
|
- `.cursor/commands/cc*.md` + `.cursor/hooks.json` + `.cursor/rules/cclaw-workflow.mdc`
|
|
129
129
|
- `.opencode/commands/cc*.md` + `.opencode/plugins/cclaw-plugin.mjs`
|
|
130
|
-
- `.agents/skills/
|
|
131
|
-
or description-based auto-matching
|
|
132
|
-
|
|
130
|
+
- `.agents/skills/cc*/SKILL.md` + `.codex/hooks.json` (Codex; skills are
|
|
131
|
+
activated via `/use cc` or description-based auto-matching. Hooks
|
|
132
|
+
require Codex CLI ≥ v0.114 and `[features] codex_hooks = true` in
|
|
133
|
+
`~/.codex/config.toml`; `cclaw init --codex` offers to patch that flag
|
|
134
|
+
for you. `.codex/commands/` and the legacy `.agents/skills/cclaw-cc*/`
|
|
135
|
+
folders are auto-cleaned on sync.)
|
|
133
136
|
- `AGENTS.md` with a managed routing block (includes a Codex-specific note)
|
|
134
137
|
|
|
135
138
|
`.cclaw/config.yaml` holds every tunable key (prompt guard strictness,
|
|
@@ -356,8 +359,8 @@ closes every real gap with a documented fallback — not a silent waiver.
|
|
|
356
359
|
|---|---|---|---|---|---|
|
|
357
360
|
| Claude Code | full (named subagents) | `native` | full | `AskUserQuestion` | [`claude-playbook.md`](./src/content/harness-playbooks.ts) |
|
|
358
361
|
| Cursor | generic Task dispatcher | `generic-dispatch` | full | `AskQuestion` | `cursor-playbook.md` |
|
|
359
|
-
| OpenCode | plugin / in-session | `role-switch` | plugin |
|
|
360
|
-
| OpenAI Codex | in-session only | `role-switch` (evidenceRefs required) |
|
|
362
|
+
| OpenCode | plugin / in-session | `role-switch` | plugin | `question` (permission-gated; `permission.question: "allow"`) | `opencode-playbook.md` |
|
|
363
|
+
| OpenAI Codex | in-session only | `role-switch` (evidenceRefs required) | limited (Bash-only `PreToolUse`/`PostToolUse`; requires `codex_hooks` feature flag) | `request_user_input` (experimental; Plan / Collaboration mode) | `codex-playbook.md` |
|
|
361
364
|
|
|
362
365
|
What the fallbacks mean:
|
|
363
366
|
|
|
@@ -380,16 +383,21 @@ What the fallbacks mean:
|
|
|
380
383
|
harness declares it. Currently unused — v0.33 removed the old
|
|
381
384
|
Codex-only auto-waiver path.
|
|
382
385
|
|
|
383
|
-
> **Codex note (v0.
|
|
384
|
-
>
|
|
385
|
-
>
|
|
386
|
-
>
|
|
387
|
-
> `/use
|
|
388
|
-
>
|
|
389
|
-
>
|
|
390
|
-
>
|
|
391
|
-
>
|
|
392
|
-
>
|
|
386
|
+
> **Codex note (v0.40+).** Codex CLI deprecated custom prompts in v0.89
|
|
387
|
+
> (Jan 2026), but Codex ≥ v0.114 (Mar 2026) grew an experimental
|
|
388
|
+
> lifecycle hooks API. cclaw installs Codex entry points as native
|
|
389
|
+
> **skills** under `.agents/skills/cc*/SKILL.md` (invoke with `/use cc`,
|
|
390
|
+
> `/use cc-next`, `/use cc-view`, `/use cc-ops`, `/use cc-ideate`, or
|
|
391
|
+
> by typing `/cc …` in plain text — Codex auto-matches from the skill
|
|
392
|
+
> description) **and** writes `.codex/hooks.json` so session-start
|
|
393
|
+
> rehydration, stop-checkpoint, prompt-guard, workflow-guard, and
|
|
394
|
+
> context-monitor fire automatically — as long as you enable the
|
|
395
|
+
> `codex_hooks` feature flag in `~/.codex/config.toml`. `cclaw init
|
|
396
|
+
> --codex` asks for consent before patching that file. Codex's
|
|
397
|
+
> `PreToolUse`/`PostToolUse` are Bash-only; the stage skills compensate
|
|
398
|
+
> for `Write`/`Edit`/`MCP` tool calls with explicit in-turn checks. Run
|
|
399
|
+
> `cclaw doctor` to see the current state of hooks, the feature flag,
|
|
400
|
+
> and any legacy layout to clean up.
|
|
393
401
|
|
|
394
402
|
The full capability matrix lives in
|
|
395
403
|
[`docs/harnesses.md`](./docs/harnesses.md). Per-harness playbooks are
|
package/dist/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ import { CCLAW_VERSION, RUNTIME_ROOT } from "./constants.js";
|
|
|
15
15
|
import { createDefaultConfig } from "./config.js";
|
|
16
16
|
import { detectHarnesses } from "./init-detect.js";
|
|
17
17
|
import { HARNESS_ADAPTERS } from "./harness-adapters.js";
|
|
18
|
+
import { classifyCodexHooksFlag, codexConfigPath, patchCodexHooksFlag, readCodexConfig, writeCodexConfig } from "./codex-feature-flag.js";
|
|
18
19
|
import { runEval } from "./eval/runner.js";
|
|
19
20
|
import { createStderrProgressLogger } from "./eval/progress.js";
|
|
20
21
|
import { writeBaselinesFromReport } from "./eval/baseline.js";
|
|
@@ -152,7 +153,7 @@ function buildInitSurfacePreview(harnesses) {
|
|
|
152
153
|
for (const harness of harnesses) {
|
|
153
154
|
const adapter = HARNESS_ADAPTERS[harness];
|
|
154
155
|
if (adapter.shimKind === "skill") {
|
|
155
|
-
lines.push(`${adapter.commandDir}/
|
|
156
|
+
lines.push(`${adapter.commandDir}/cc*/SKILL.md`);
|
|
156
157
|
}
|
|
157
158
|
else {
|
|
158
159
|
lines.push(`${adapter.commandDir}/cc*.md`);
|
|
@@ -164,9 +165,12 @@ function buildInitSurfacePreview(harnesses) {
|
|
|
164
165
|
lines.push(".cursor/hooks.json");
|
|
165
166
|
lines.push(".cursor/rules/cclaw-workflow.mdc");
|
|
166
167
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
168
|
+
if (harness === "codex") {
|
|
169
|
+
// v0.40.0: .codex/hooks.json is managed again now that Codex CLI
|
|
170
|
+
// grew a real hooks API (v0.114+, behind the `codex_hooks`
|
|
171
|
+
// feature flag). Legacy `.codex/commands/*` is still auto-cleaned.
|
|
172
|
+
lines.push(".codex/hooks.json (requires `codex_hooks = true` in ~/.codex/config.toml)");
|
|
173
|
+
}
|
|
170
174
|
if (harness === "opencode") {
|
|
171
175
|
lines.push(".opencode/plugins/cclaw-plugin.mjs");
|
|
172
176
|
lines.push("opencode.json(.c) plugin registration");
|
|
@@ -207,6 +211,85 @@ async function promptInitConfig(defaults, ctx) {
|
|
|
207
211
|
rl.close();
|
|
208
212
|
}
|
|
209
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* When Codex is one of the installed harnesses, check the Codex CLI
|
|
216
|
+
* config file for the `codex_hooks` feature flag. If it is missing or
|
|
217
|
+
* disabled, offer to patch it in with the user's explicit consent.
|
|
218
|
+
*
|
|
219
|
+
* The function is deliberately advisory: it never fails init — the worst
|
|
220
|
+
* case is that Codex runs without the hooks engine, which is exactly
|
|
221
|
+
* how v0.39.x already shipped. We always print a resolution hint so
|
|
222
|
+
* the user knows what to do next regardless of which branch was taken.
|
|
223
|
+
*/
|
|
224
|
+
async function maybeEnableCodexHooksFlag(harnesses, parsed, ctx) {
|
|
225
|
+
if (!harnesses || !harnesses.includes("codex"))
|
|
226
|
+
return;
|
|
227
|
+
const configPath = codexConfigPath();
|
|
228
|
+
let existing;
|
|
229
|
+
try {
|
|
230
|
+
existing = await readCodexConfig(configPath);
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
ctx.stdout.write(`note: Could not read ${configPath} to check the codex_hooks flag: ` +
|
|
234
|
+
`${err instanceof Error ? err.message : String(err)}\n`);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const state = classifyCodexHooksFlag(existing);
|
|
238
|
+
if (state === "enabled") {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const humanState = state === "missing-file"
|
|
242
|
+
? "Codex config file does not exist yet"
|
|
243
|
+
: state === "missing-section"
|
|
244
|
+
? "no [features] section"
|
|
245
|
+
: state === "missing-key"
|
|
246
|
+
? "no codex_hooks key"
|
|
247
|
+
: "codex_hooks is not enabled";
|
|
248
|
+
const instructions = `To enable Codex hooks manually later, ensure ${configPath} contains:\n` +
|
|
249
|
+
` [features]\n codex_hooks = true\n`;
|
|
250
|
+
if (parsed.interactive === false) {
|
|
251
|
+
ctx.stdout.write(`note: codex_hooks feature flag is not enabled (${humanState}).\n` +
|
|
252
|
+
` cclaw wrote .codex/hooks.json, but Codex will ignore it until you enable the flag.\n` +
|
|
253
|
+
` ${instructions}`);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (!isInitPromptAllowed(ctx)) {
|
|
257
|
+
ctx.stdout.write(`note: codex_hooks feature flag is not enabled (${humanState}).\n` +
|
|
258
|
+
` cclaw wrote .codex/hooks.json, but Codex will ignore it until you enable the flag.\n` +
|
|
259
|
+
` ${instructions}`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const rl = createInterface({
|
|
263
|
+
input: process.stdin,
|
|
264
|
+
output: ctx.stdout
|
|
265
|
+
});
|
|
266
|
+
try {
|
|
267
|
+
const answer = (await rl.question(`\nCodex CLI hooks are off (${humanState}).\n` +
|
|
268
|
+
`Enable [features] codex_hooks = true in ${configPath} now? [y/N]: `)).trim().toLowerCase();
|
|
269
|
+
const yes = answer === "y" || answer === "yes";
|
|
270
|
+
if (!yes) {
|
|
271
|
+
ctx.stdout.write(`Leaving ${configPath} untouched. ${instructions}`);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const { updated, changed } = patchCodexHooksFlag(existing);
|
|
275
|
+
if (!changed) {
|
|
276
|
+
ctx.stdout.write(`codex_hooks is already enabled — no changes written.\n`);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
await writeCodexConfig(configPath, updated);
|
|
281
|
+
ctx.stdout.write(`Enabled [features] codex_hooks = true in ${configPath}.\n`);
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
ctx.stdout.write(`Could not write ${configPath}: ` +
|
|
285
|
+
`${err instanceof Error ? err.message : String(err)}\n` +
|
|
286
|
+
`${instructions}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
finally {
|
|
290
|
+
rl.close();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
210
293
|
async function resolveInitInputs(parsed, ctx) {
|
|
211
294
|
const detectedHarnesses = parsed.harnesses ? [] : await detectHarnesses(ctx.cwd);
|
|
212
295
|
const autoHarnesses = parsed.harnesses
|
|
@@ -744,6 +827,7 @@ async function runCommand(parsed, ctx) {
|
|
|
744
827
|
}
|
|
745
828
|
const trackNote = effectiveTrack ? ` (track=${effectiveTrack})` : "";
|
|
746
829
|
info(ctx, `Initialized .cclaw runtime and generated harness shims${trackNote}`);
|
|
830
|
+
await maybeEnableCodexHooksFlag(effectiveHarnesses, parsed, ctx);
|
|
747
831
|
return 0;
|
|
748
832
|
}
|
|
749
833
|
if (command === "sync") {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manage the `codex_hooks` feature flag in `~/.codex/config.toml`.
|
|
3
|
+
*
|
|
4
|
+
* Codex CLI ≥ v0.114 (Mar 2026) exposes lifecycle hooks via
|
|
5
|
+
* `.codex/hooks.json`, but the hooks engine is inert unless the user has
|
|
6
|
+
* opted into it with:
|
|
7
|
+
*
|
|
8
|
+
* ```toml
|
|
9
|
+
* [features]
|
|
10
|
+
* codex_hooks = true
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* in `$CODEX_HOME/config.toml` (default: `~/.codex/config.toml`).
|
|
14
|
+
* cclaw's `init --codex` prompts the user to flip this flag for them;
|
|
15
|
+
* this module owns the detection / mutation code so the prompt logic in
|
|
16
|
+
* `cli.ts` stays small and testable.
|
|
17
|
+
*
|
|
18
|
+
* The TOML mutations here are intentionally surgical — we never reparse
|
|
19
|
+
* or rewrite the whole document. A deliberately narrow regex based
|
|
20
|
+
* approach lets the function stay dependency-free and preserves the
|
|
21
|
+
* user's comments, whitespace, and custom key ordering.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Absolute path of the Codex config file. Respects `$CODEX_HOME` when
|
|
25
|
+
* present (the only override Codex CLI documents); falls back to
|
|
26
|
+
* `~/.codex/config.toml` otherwise.
|
|
27
|
+
*/
|
|
28
|
+
export declare function codexConfigPath(env?: NodeJS.ProcessEnv): string;
|
|
29
|
+
export type CodexHooksFlagState = "enabled" | "disabled" | "missing-key" | "missing-section" | "missing-file";
|
|
30
|
+
/**
|
|
31
|
+
* Inspect a TOML document and decide which of the five canonical states
|
|
32
|
+
* it represents. Comments and blank lines are ignored. Only the first
|
|
33
|
+
* `[features]` section is considered — duplicates are technically invalid
|
|
34
|
+
* TOML and Codex rejects them, so cclaw does not try to be clever there.
|
|
35
|
+
*/
|
|
36
|
+
export declare function classifyCodexHooksFlag(toml: string | null): CodexHooksFlagState;
|
|
37
|
+
/**
|
|
38
|
+
* Return a TOML document with `[features] codex_hooks = true` set.
|
|
39
|
+
* Preserves all other content verbatim:
|
|
40
|
+
* - If the document lacks a `[features]` section, we append one at the
|
|
41
|
+
* end of the file (separated by a blank line).
|
|
42
|
+
* - If `[features]` exists without `codex_hooks`, we insert the key
|
|
43
|
+
* immediately after the header.
|
|
44
|
+
* - If `codex_hooks` exists with any non-`true` value, we rewrite
|
|
45
|
+
* just that line.
|
|
46
|
+
* - If the flag is already `true`, the input is returned unchanged.
|
|
47
|
+
*/
|
|
48
|
+
export declare function patchCodexHooksFlag(toml: string | null): {
|
|
49
|
+
updated: string;
|
|
50
|
+
changed: boolean;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Read the Codex config, return `null` when the file does not exist.
|
|
54
|
+
* All other read errors propagate so callers can surface a useful
|
|
55
|
+
* message instead of silently degrading.
|
|
56
|
+
*/
|
|
57
|
+
export declare function readCodexConfig(configPath: string): Promise<string | null>;
|
|
58
|
+
export declare function writeCodexConfig(configPath: string, content: string): Promise<void>;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manage the `codex_hooks` feature flag in `~/.codex/config.toml`.
|
|
3
|
+
*
|
|
4
|
+
* Codex CLI ≥ v0.114 (Mar 2026) exposes lifecycle hooks via
|
|
5
|
+
* `.codex/hooks.json`, but the hooks engine is inert unless the user has
|
|
6
|
+
* opted into it with:
|
|
7
|
+
*
|
|
8
|
+
* ```toml
|
|
9
|
+
* [features]
|
|
10
|
+
* codex_hooks = true
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* in `$CODEX_HOME/config.toml` (default: `~/.codex/config.toml`).
|
|
14
|
+
* cclaw's `init --codex` prompts the user to flip this flag for them;
|
|
15
|
+
* this module owns the detection / mutation code so the prompt logic in
|
|
16
|
+
* `cli.ts` stays small and testable.
|
|
17
|
+
*
|
|
18
|
+
* The TOML mutations here are intentionally surgical — we never reparse
|
|
19
|
+
* or rewrite the whole document. A deliberately narrow regex based
|
|
20
|
+
* approach lets the function stay dependency-free and preserves the
|
|
21
|
+
* user's comments, whitespace, and custom key ordering.
|
|
22
|
+
*/
|
|
23
|
+
import fs from "node:fs/promises";
|
|
24
|
+
import os from "node:os";
|
|
25
|
+
import path from "node:path";
|
|
26
|
+
/**
|
|
27
|
+
* Absolute path of the Codex config file. Respects `$CODEX_HOME` when
|
|
28
|
+
* present (the only override Codex CLI documents); falls back to
|
|
29
|
+
* `~/.codex/config.toml` otherwise.
|
|
30
|
+
*/
|
|
31
|
+
export function codexConfigPath(env = process.env) {
|
|
32
|
+
const codexHome = env.CODEX_HOME && env.CODEX_HOME.trim().length > 0
|
|
33
|
+
? env.CODEX_HOME
|
|
34
|
+
: path.join(os.homedir(), ".codex");
|
|
35
|
+
return path.join(codexHome, "config.toml");
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Inspect a TOML document and decide which of the five canonical states
|
|
39
|
+
* it represents. Comments and blank lines are ignored. Only the first
|
|
40
|
+
* `[features]` section is considered — duplicates are technically invalid
|
|
41
|
+
* TOML and Codex rejects them, so cclaw does not try to be clever there.
|
|
42
|
+
*/
|
|
43
|
+
export function classifyCodexHooksFlag(toml) {
|
|
44
|
+
if (toml === null) {
|
|
45
|
+
return "missing-file";
|
|
46
|
+
}
|
|
47
|
+
const lines = toml.split(/\r?\n/);
|
|
48
|
+
let inFeaturesSection = false;
|
|
49
|
+
let sawFeaturesHeader = false;
|
|
50
|
+
for (const rawLine of lines) {
|
|
51
|
+
const stripped = stripTomlComment(rawLine).trim();
|
|
52
|
+
if (stripped.length === 0)
|
|
53
|
+
continue;
|
|
54
|
+
const headerMatch = /^\[\s*([A-Za-z0-9_.-]+)\s*\]$/u.exec(stripped);
|
|
55
|
+
if (headerMatch) {
|
|
56
|
+
const section = headerMatch[1];
|
|
57
|
+
if (section === "features") {
|
|
58
|
+
inFeaturesSection = true;
|
|
59
|
+
sawFeaturesHeader = true;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
inFeaturesSection = false;
|
|
63
|
+
}
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (inFeaturesSection) {
|
|
67
|
+
const keyMatch = /^codex_hooks\s*=\s*(.*)$/u.exec(stripped);
|
|
68
|
+
if (keyMatch) {
|
|
69
|
+
const value = keyMatch[1].trim().toLowerCase();
|
|
70
|
+
return value === "true" ? "enabled" : "disabled";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (sawFeaturesHeader)
|
|
75
|
+
return "missing-key";
|
|
76
|
+
return "missing-section";
|
|
77
|
+
}
|
|
78
|
+
function stripTomlComment(line) {
|
|
79
|
+
// Naive but sufficient for our narrow use case: we only read cclaw's
|
|
80
|
+
// own writes back, and cclaw never emits `=` after a `#` inside a
|
|
81
|
+
// string literal in config.toml. If a user has complex string values
|
|
82
|
+
// with `#` inside them, worst case we trip `classifyCodexHooksFlag`
|
|
83
|
+
// and prompt them again — non-destructive.
|
|
84
|
+
const hashIndex = line.indexOf("#");
|
|
85
|
+
return hashIndex === -1 ? line : line.slice(0, hashIndex);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Return a TOML document with `[features] codex_hooks = true` set.
|
|
89
|
+
* Preserves all other content verbatim:
|
|
90
|
+
* - If the document lacks a `[features]` section, we append one at the
|
|
91
|
+
* end of the file (separated by a blank line).
|
|
92
|
+
* - If `[features]` exists without `codex_hooks`, we insert the key
|
|
93
|
+
* immediately after the header.
|
|
94
|
+
* - If `codex_hooks` exists with any non-`true` value, we rewrite
|
|
95
|
+
* just that line.
|
|
96
|
+
* - If the flag is already `true`, the input is returned unchanged.
|
|
97
|
+
*/
|
|
98
|
+
export function patchCodexHooksFlag(toml) {
|
|
99
|
+
const initial = toml ?? "";
|
|
100
|
+
const state = classifyCodexHooksFlag(toml);
|
|
101
|
+
if (state === "enabled") {
|
|
102
|
+
return { updated: initial, changed: false };
|
|
103
|
+
}
|
|
104
|
+
if (state === "missing-file" || state === "missing-section") {
|
|
105
|
+
const prefix = initial.length === 0
|
|
106
|
+
? ""
|
|
107
|
+
: initial.endsWith("\n") ? initial : `${initial}\n`;
|
|
108
|
+
const separator = initial.trim().length === 0 ? "" : "\n";
|
|
109
|
+
const block = `${separator}[features]\ncodex_hooks = true\n`;
|
|
110
|
+
return { updated: `${prefix}${block}`, changed: true };
|
|
111
|
+
}
|
|
112
|
+
if (state === "missing-key") {
|
|
113
|
+
const updated = insertKeyInFeaturesSection(initial);
|
|
114
|
+
return { updated, changed: true };
|
|
115
|
+
}
|
|
116
|
+
const updated = replaceCodexHooksLineInFeaturesSection(initial);
|
|
117
|
+
return { updated, changed: true };
|
|
118
|
+
}
|
|
119
|
+
function insertKeyInFeaturesSection(toml) {
|
|
120
|
+
// Walk into `[features]`, remember the index of the last key / non-blank
|
|
121
|
+
// line inside that section, and splice `codex_hooks = true` immediately
|
|
122
|
+
// after it. This keeps the inserted key adjacent to existing features,
|
|
123
|
+
// never stranded after a blank line or pushed down past a later section
|
|
124
|
+
// header. If `[features]` is empty, we insert right after its header.
|
|
125
|
+
const lines = toml.split(/\r?\n/);
|
|
126
|
+
let inFeaturesSection = false;
|
|
127
|
+
let featuresHeaderIndex = -1;
|
|
128
|
+
let lastFeatureKeyIndex = -1;
|
|
129
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
130
|
+
const rawLine = lines[index];
|
|
131
|
+
const stripped = stripTomlComment(rawLine).trim();
|
|
132
|
+
const headerMatch = /^\[\s*([A-Za-z0-9_.-]+)\s*\]$/u.exec(stripped);
|
|
133
|
+
if (headerMatch) {
|
|
134
|
+
if (inFeaturesSection)
|
|
135
|
+
break;
|
|
136
|
+
if (headerMatch[1] === "features") {
|
|
137
|
+
inFeaturesSection = true;
|
|
138
|
+
featuresHeaderIndex = index;
|
|
139
|
+
lastFeatureKeyIndex = index;
|
|
140
|
+
}
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (inFeaturesSection && stripped.length > 0) {
|
|
144
|
+
lastFeatureKeyIndex = index;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (featuresHeaderIndex === -1) {
|
|
148
|
+
// caller should have short-circuited before getting here; defensive
|
|
149
|
+
return toml;
|
|
150
|
+
}
|
|
151
|
+
lines.splice(lastFeatureKeyIndex + 1, 0, "codex_hooks = true");
|
|
152
|
+
const joined = lines.join("\n");
|
|
153
|
+
return joined.endsWith("\n") ? joined : `${joined}\n`;
|
|
154
|
+
}
|
|
155
|
+
function replaceCodexHooksLineInFeaturesSection(toml) {
|
|
156
|
+
const lines = toml.split(/\r?\n/);
|
|
157
|
+
let inFeaturesSection = false;
|
|
158
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
159
|
+
const rawLine = lines[index];
|
|
160
|
+
const stripped = stripTomlComment(rawLine).trim();
|
|
161
|
+
const headerMatch = /^\[\s*([A-Za-z0-9_.-]+)\s*\]$/u.exec(stripped);
|
|
162
|
+
if (headerMatch) {
|
|
163
|
+
inFeaturesSection = headerMatch[1] === "features";
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (inFeaturesSection && /^codex_hooks\s*=/u.test(stripped)) {
|
|
167
|
+
const indent = /^\s*/u.exec(rawLine)?.[0] ?? "";
|
|
168
|
+
lines[index] = `${indent}codex_hooks = true`;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const joined = lines.join("\n");
|
|
172
|
+
return joined.endsWith("\n") ? joined : `${joined}\n`;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Read the Codex config, return `null` when the file does not exist.
|
|
176
|
+
* All other read errors propagate so callers can surface a useful
|
|
177
|
+
* message instead of silently degrading.
|
|
178
|
+
*/
|
|
179
|
+
export async function readCodexConfig(configPath) {
|
|
180
|
+
try {
|
|
181
|
+
return await fs.readFile(configPath, "utf8");
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
if (err.code === "ENOENT") {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
export async function writeCodexConfig(configPath, content) {
|
|
191
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
192
|
+
await fs.writeFile(configPath, content, "utf8");
|
|
193
|
+
}
|
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", ".agents/skills/
|
|
17
|
+
export declare const REQUIRED_GITIGNORE_PATTERNS: readonly ["# cclaw generated artifacts", ".cclaw/", "# cclaw evals: user-owned, track in git", "!.cclaw/evals/", "!.cclaw/evals/config.yaml", "!.cclaw/evals/corpus/", "!.cclaw/evals/corpus/**", "!.cclaw/evals/rubrics/", "!.cclaw/evals/rubrics/**", "!.cclaw/evals/baselines/", "!.cclaw/evals/baselines/**", ".claude/commands/cc-*.md", ".claude/commands/cc.md", ".cursor/commands/cc-*.md", ".cursor/commands/cc.md", ".opencode/commands/cc-*.md", ".opencode/commands/cc.md", ".agents/skills/cc/SKILL.md", ".agents/skills/cc-*/SKILL.md", ".claude/hooks/hooks.json", ".cursor/hooks.json", ".codex/hooks.json", ".opencode/plugins/cclaw-plugin.mjs", ".cursor/rules/cclaw-workflow.mdc"];
|
|
18
18
|
export declare const COMMAND_FILE_ORDER: FlowStage[];
|
|
19
19
|
export declare const UTILITY_COMMANDS: readonly ["learn", "next", "ideate", "view", "status", "tree", "diff", "ops", "feature", "tdd-log", "retro", "compound", "archive", "rewind"];
|
|
20
20
|
export declare const SUBAGENT_SKILL_FOLDERS: readonly ["subagent-dev", "parallel-dispatch"];
|
package/dist/constants.js
CHANGED
|
@@ -91,12 +91,15 @@ export const REQUIRED_GITIGNORE_PATTERNS = [
|
|
|
91
91
|
".cursor/commands/cc.md",
|
|
92
92
|
".opencode/commands/cc-*.md",
|
|
93
93
|
".opencode/commands/cc.md",
|
|
94
|
-
// Codex uses skill-kind shims under `.agents/skills/
|
|
95
|
-
// v0.
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
// Codex uses skill-kind shims under `.agents/skills/cc*/` since
|
|
95
|
+
// v0.40.0 (renamed from the `cclaw-cc*` layout in v0.39.0/v0.39.1).
|
|
96
|
+
// `cclaw sync` and `cclaw uninstall` both auto-remove the legacy
|
|
97
|
+
// `cclaw-cc*` directories.
|
|
98
|
+
".agents/skills/cc/SKILL.md",
|
|
99
|
+
".agents/skills/cc-*/SKILL.md",
|
|
98
100
|
".claude/hooks/hooks.json",
|
|
99
101
|
".cursor/hooks.json",
|
|
102
|
+
".codex/hooks.json",
|
|
100
103
|
".opencode/plugins/cclaw-plugin.mjs",
|
|
101
104
|
".cursor/rules/cclaw-workflow.mdc"
|
|
102
105
|
];
|
|
@@ -39,8 +39,10 @@ the user can approve individual lifts, accept-all, or skip.
|
|
|
39
39
|
"Drift check" section in the skill): confirm the lift target file is
|
|
40
40
|
current, spot-check the repo for contradictions, demote stale clusters
|
|
41
41
|
into a new superseding entry instead of a lift.
|
|
42
|
-
6. Otherwise, present **one** structured ask
|
|
43
|
-
|
|
42
|
+
6. Otherwise, present **one** structured ask via the harness's native ask
|
|
43
|
+
tool (\`AskUserQuestion\` / \`AskQuestion\` / \`question\` /
|
|
44
|
+
\`request_user_input\`; plain-text lettered list as fallback) summarising
|
|
45
|
+
all candidates at once:
|
|
44
46
|
- \`apply-all\` (default) — apply every listed lift,
|
|
45
47
|
- \`apply-selected\` — prompt per-candidate,
|
|
46
48
|
- \`skip\` — record a skip reason and advance without changes.
|