@cleocode/adapters 2026.10.6 → 2026.10.7

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.
@@ -1 +1 @@
1
- {"version":3,"file":"heavy-command-hook-delivery.d.ts","sourceRoot":"","sources":["../../../src/providers/shared/heavy-command-hook-delivery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAOH,OAAO,KAAK,EACV,oBAAoB,EACpB,wBAAwB,EACxB,iBAAiB,EACjB,oBAAoB,EACpB,wBAAwB,EACxB,wBAAwB,EACxB,mBAAmB,EACpB,MAAM,qBAAqB,CAAC;AA0B7B,8DAA8D;AAC9D,eAAO,MAAM,6BAA6B,EAAE,SAAS,wBAAwB,EAK3E,CAAC;AAEH,gEAAgE;AAChE,eAAO,MAAM,sBAAsB,yCAAyC,CAAC;AAE7E,KAAK,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;AAgBxD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,wBAAwB,EAClC,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,GAAiB,GACrB,MAAM,CAWR;AAED,uEAAuE;AACvE,MAAM,WAAW,kBAAkB;IACjC,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,8DAA8D;IAC9D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,wBAAwB,EAClC,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,GAAiB,GACrB,kBAAkB,CAgDpB;AAgED;;;;;;;;;;GAUG;AACH,wBAAsB,4BAA4B,CAChD,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,oBAAoB,EAC1B,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,SAAS,wBAAwB,EAAE,CAAC,CAwB9C;AA2MD;;;;;;;;GAQG;AACH,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,oBAAoB,EAC1B,OAAO,GAAE,wBAA6B,GACrC,SAAS,mBAAmB,EAAE,CAchC;AAwCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,wBAA6B,GACrC,iBAAiB,CA6CnB;AAED,+DAA+D;AAC/D,eAAO,MAAM,oBAAoB,EAAE,oBAIjC,CAAC"}
1
+ {"version":3,"file":"heavy-command-hook-delivery.d.ts","sourceRoot":"","sources":["../../../src/providers/shared/heavy-command-hook-delivery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAOH,OAAO,KAAK,EACV,oBAAoB,EACpB,wBAAwB,EACxB,iBAAiB,EACjB,oBAAoB,EACpB,wBAAwB,EACxB,wBAAwB,EACxB,mBAAmB,EACpB,MAAM,qBAAqB,CAAC;AA2B7B,8DAA8D;AAC9D,eAAO,MAAM,6BAA6B,EAAE,SAAS,wBAAwB,EAK3E,CAAC;AAEH,gEAAgE;AAChE,eAAO,MAAM,sBAAsB,yCAAyC,CAAC;AAE7E,KAAK,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;AAgBxD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,wBAAwB,EAClC,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,GAAiB,GACrB,MAAM,CAWR;AAED,uEAAuE;AACvE,MAAM,WAAW,kBAAkB;IACjC,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,8DAA8D;IAC9D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,wBAAwB,EAClC,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,GAAiB,GACrB,kBAAkB,CAgDpB;AAsFD;;;;;;;;;;GAUG;AACH,wBAAsB,4BAA4B,CAChD,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,oBAAoB,EAC1B,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,SAAS,wBAAwB,EAAE,CAAC,CAwB9C;AA2MD;;;;;;;;GAQG;AACH,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,oBAAoB,EAC1B,OAAO,GAAE,wBAA6B,GACrC,SAAS,mBAAmB,EAAE,CAchC;AAwCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,wBAA6B,GACrC,iBAAiB,CA6CnB;AAED,+DAA+D;AAC/D,eAAO,MAAM,oBAAoB,EAAE,oBAIjC,CAAC"}
@@ -145,6 +145,29 @@ export declare function isHeavyHookObject(hook: unknown): boolean;
145
145
  * @returns what changed.
146
146
  */
147
147
  export declare function syncJsonHeavyCommandHook(configPath: string, provider: 'claude-code' | 'codex', mode: HeavyCommandHookMode): Promise<HeavyHookSyncResult>;
148
+ /** The command CLEO's ask-enforce Stop hook runs (T13420); also its marker. */
149
+ export declare const ASK_ENFORCE_HOOK_ID = "cleo hook ask-enforce";
150
+ /**
151
+ * CLEO's ask-enforce Stop hook object (T13420). The script prints the hook's
152
+ * answer only when it is a JSON object and always exits 0, so a machine
153
+ * without `cleo`, or an older `cleo` that lacks the subcommand, never blocks.
154
+ *
155
+ * @param provider - `claude-code` or `codex` (Codex runs hooks through the login shell).
156
+ */
157
+ export declare function askEnforceHookObject(provider: 'claude-code' | 'codex'): Record<string, unknown>;
158
+ /** Whether one hook object is CLEO's ask-enforce Stop hook. */
159
+ export declare function isAskEnforceHookObject(hook: unknown): boolean;
160
+ /**
161
+ * Add, refresh or (`install: false`) remove CLEO's ask-enforce Stop hook in a
162
+ * JSON hook config (`hooks.Stop`), through CAAMP's locked atomic writer. Only
163
+ * CLEO's own hook object is touched; a malformed file throws untouched.
164
+ *
165
+ * @param configPath - the config file (created when missing, unless removing).
166
+ * @param provider - `claude-code` or `codex`.
167
+ * @param install - `false` removes the hook.
168
+ * @returns what changed.
169
+ */
170
+ export declare function syncJsonAskEnforceHook(configPath: string, provider: 'claude-code' | 'codex', install: boolean): Promise<HeavyHookSyncResult>;
148
171
  /** Claude Code's per-machine settings file, relative to the project. */
149
172
  export declare const CLAUDE_LOCAL_SETTINGS = ".claude/settings.local.json";
150
173
  /** First line of the `info/exclude` block CLEO adds for `settings.local.json` (and alone may remove). */
@@ -1 +1 @@
1
- {"version":3,"file":"heavy-command-hook-install.d.ts","sourceRoot":"","sources":["../../../src/providers/shared/heavy-command-hook-install.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"heavy-command-hook-install.d.ts","sourceRoot":"","sources":["../../../src/providers/shared/heavy-command-hook-install.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/adapters",
3
- "version": "2026.10.6",
3
+ "version": "2026.10.7",
4
4
  "description": "Unified provider adapters for CLEO (Claude Code, OpenCode, Cursor)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -19,9 +19,9 @@
19
19
  "@ai-sdk/anthropic": "^3.0.69",
20
20
  "@ai-sdk/openai": "^2.0.53",
21
21
  "ai": "^6.0.168",
22
- "@cleocode/caamp": "2026.10.6",
23
- "@cleocode/contracts": "2026.10.6",
24
- "@cleocode/paths": "2026.10.6"
22
+ "@cleocode/contracts": "2026.10.7",
23
+ "@cleocode/paths": "2026.10.7",
24
+ "@cleocode/caamp": "2026.10.7"
25
25
  },
26
26
  "license": "MIT",
27
27
  "engines": {
@@ -34,7 +34,7 @@
34
34
  "devDependencies": {
35
35
  "@types/node": "^24.3.0",
36
36
  "vitest": "^4.1.4",
37
- "@cleocode/playbooks": "2026.10.6"
37
+ "@cleocode/playbooks": "2026.10.7"
38
38
  },
39
39
  "repository": {
40
40
  "type": "git",
@@ -96,6 +96,22 @@ describe('syncProjectHeavyCommandHooks', () => {
96
96
  });
97
97
  });
98
98
 
99
+ it('installs the ask-enforce Stop hook beside it for Claude Code and Codex (T13420)', async () => {
100
+ useAll();
101
+ await syncProjectHeavyCommandHooks(project, 'rewrite', { env });
102
+ const stop = (file: string) =>
103
+ JSON.stringify(JSON.parse(readFileSync(file, 'utf-8')).hooks.Stop ?? []);
104
+ const claude = join(project, '.claude', 'settings.local.json');
105
+ const codex = join(project, '.codex', 'hooks.json');
106
+ expect(stop(claude)).toContain('cleo hook ask-enforce --provider claude-code');
107
+ expect(stop(codex)).toContain('cleo hook ask-enforce --provider codex');
108
+ await syncProjectHeavyCommandHooks(project, 'rewrite', {
109
+ env: { ...env, CLEO_ASK_ENFORCE: 'off' },
110
+ });
111
+ expect(stop(claude)).not.toContain('ask-enforce');
112
+ expect(stop(codex)).not.toContain('ask-enforce');
113
+ });
114
+
99
115
  it('a .codex FILE blocks only Codex, with the reason and remedy; the others install', async () => {
100
116
  useAll();
101
117
  writeFileSync(join(project, '.codex'), '');
@@ -46,6 +46,8 @@ import { CodexInstallProvider } from '../../codex/install.js';
46
46
  import { CursorInstallProvider } from '../../cursor/install.js';
47
47
  import { OpenCodeInstallProvider } from '../../opencode/install.js';
48
48
  import {
49
+ ASK_ENFORCE_HOOK_ID,
50
+ askEnforceHookObject,
49
51
  clearOlderCleoMarkers,
50
52
  excludeLocalSettingsFromGit,
51
53
  HEAVY_COMMAND_HOOK_TIMEOUT_SEC,
@@ -57,6 +59,7 @@ import {
57
59
  OLDER_CLEO_HOOK_ANSWER,
58
60
  OLDER_CLEO_MARKER_PREFIX,
59
61
  OPENCODE_HEAVY_COMMAND_PLUGIN,
62
+ syncJsonAskEnforceHook,
60
63
  syncJsonHeavyCommandHook,
61
64
  syncOpencodeHeavyCommandPlugin,
62
65
  } from '../heavy-command-hook-install.js';
@@ -327,6 +330,43 @@ describe.each(SHELLS)('heavyCommandHookCommand under %s', (shell) => {
327
330
  });
328
331
  });
329
332
 
333
+ describe('syncJsonAskEnforceHook (T13420)', () => {
334
+ const read = (p: string) => JSON.parse(readFileSync(p, 'utf-8'));
335
+
336
+ it('adds one Stop entry beside the heavy-command hook, then is idempotent', async () => {
337
+ const path = join(dir, 'project', '.claude', 'settings.local.json');
338
+ await syncJsonHeavyCommandHook(path, 'claude-code', 'rewrite');
339
+ expect(await syncJsonAskEnforceHook(path, 'claude-code', true)).toBe('installed');
340
+ expect(read(path).hooks).toEqual({
341
+ PreToolUse: [heavyCommandHookEntry('claude-code')],
342
+ Stop: [{ hooks: [askEnforceHookObject('claude-code')] }],
343
+ });
344
+ expect(await syncJsonAskEnforceHook(path, 'claude-code', true)).toBe('unchanged');
345
+ });
346
+
347
+ it('keeps user Stop hooks, refreshes a stale CLEO one, and removes only its own', async () => {
348
+ const path = join(dir, 'hooks.json');
349
+ const user = { hooks: [{ type: 'command', command: './notify.sh' }] };
350
+ const stale = { hooks: [{ type: 'command', command: `${ASK_ENFORCE_HOOK_ID} # cleo-hook` }] };
351
+ writeFileSync(path, JSON.stringify({ hooks: { Stop: [user, stale] } }));
352
+ expect(await syncJsonAskEnforceHook(path, 'codex', true)).toBe('updated');
353
+ expect(read(path).hooks.Stop).toEqual([user, { hooks: [askEnforceHookObject('codex')] }]);
354
+ expect(await syncJsonAskEnforceHook(path, 'codex', false)).toBe('removed');
355
+ expect(read(path).hooks.Stop).toEqual([user]);
356
+ expect(await syncJsonAskEnforceHook(join(dir, 'missing.json'), 'codex', false)).toBe(
357
+ 'unchanged',
358
+ );
359
+ });
360
+
361
+ it('runs a fail-open script: exits 0 and prints only a JSON answer', () => {
362
+ const claude = String(askEnforceHookObject('claude-code').command);
363
+ expect(claude).toContain('command -v cleo >/dev/null 2>&1 || exit 0');
364
+ expect(claude).toContain(`${ASK_ENFORCE_HOOK_ID} --provider claude-code 2>/dev/null`);
365
+ expect(claude.endsWith('exit 0 # cleo-hook')).toBe(true);
366
+ expect(String(askEnforceHookObject('codex').command)).toMatch(/^\/bin\/sh -c '/);
367
+ });
368
+ });
369
+
330
370
  describe('syncJsonHeavyCommandHook', () => {
331
371
  const read = (p: string) => JSON.parse(readFileSync(p, 'utf-8'));
332
372
 
@@ -56,6 +56,7 @@ import {
56
56
  opencodeHeavyCommandPluginSource,
57
57
  syncClaudeCodeHeavyCommandHook,
58
58
  syncCodexHeavyCommandHook,
59
+ syncJsonAskEnforceHook,
59
60
  syncOpencodeHeavyCommandPlugin,
60
61
  trackedFileModified,
61
62
  } from './heavy-command-hook-install.js';
@@ -191,6 +192,27 @@ function errorText(err: unknown): string {
191
192
  }
192
193
 
193
194
  /** Sync one provider; never throws. */
195
+ /**
196
+ * Install (or remove) CLEO's ask-enforce Stop hook (T13420) in the same JSON
197
+ * config as the heavy-command hook, after that hook synced. It rides the
198
+ * heavy-command delivery: removed when that hook's mode is `off` or when
199
+ * `CLEO_ASK_ENFORCE=off`. Never fails the heavy-command outcome.
200
+ * shortcut: no separate outcome or doctor row yet; add one when a config key replaces the env switch.
201
+ */
202
+ async function syncAskEnforceHook(
203
+ provider: 'claude-code' | 'codex',
204
+ target: string,
205
+ mode: HeavyCommandHookMode,
206
+ env: Env,
207
+ ): Promise<void> {
208
+ const install = mode !== 'off' && env.CLEO_ASK_ENFORCE?.trim().toLowerCase() !== 'off';
209
+ try {
210
+ await syncJsonAskEnforceHook(target, provider, install);
211
+ } catch {
212
+ // Fail open: a config the Stop hook cannot be written to keeps the heavy-command result.
213
+ }
214
+ }
215
+
194
216
  async function syncProvider(
195
217
  provider: HeavyCommandHookProvider,
196
218
  projectDir: string,
@@ -210,6 +232,7 @@ async function syncProvider(
210
232
  : provider === 'codex'
211
233
  ? await syncCodexHeavyCommandHook(projectDir, mode)
212
234
  : syncOpencodeHeavyCommandPlugin(projectDir, mode);
235
+ if (provider !== 'opencode') await syncAskEnforceHook(provider, target, mode, env);
213
236
  return { provider, status: result, target };
214
237
  } catch (err) {
215
238
  if (err instanceof HeavyHookSharedConfigError) {
@@ -335,20 +335,24 @@ export function isHeavyHookObject(hook: unknown): boolean {
335
335
  }
336
336
 
337
337
  /**
338
- * Put `desired` in place of CLEO's first heavy-command hook object, drop any
339
- * other copies, and drop a matcher group only when CLEO's hook was all it
340
- * held. `desired: null` removes CLEO's hook everywhere.
338
+ * Put `desired` in place of CLEO's first hook object of one kind under
339
+ * `hooks.<event>`, drop any other copies, and drop a matcher group only when
340
+ * CLEO's hook was all it held. `desired: null` removes CLEO's hook everywhere.
341
341
  *
342
+ * @param event - the hook event (`PreToolUse` for heavy-command, `Stop` for ask-enforce).
343
+ * @param isMine - recognises CLEO's hook object of this kind.
342
344
  * @returns whether the groups changed, and whether CLEO's hook was found.
343
345
  */
344
346
  function placeHeavyHook(
345
347
  hooks: Record<string, unknown>,
346
348
  desired: Record<string, unknown> | null,
349
+ event = 'PreToolUse',
350
+ isMine: (hook: unknown) => boolean = isHeavyHookObject,
347
351
  ): { readonly changed: boolean; readonly found: boolean } {
348
- const groups = hooks.PreToolUse;
352
+ const groups = hooks[event];
349
353
  if (groups === undefined) return { changed: false, found: false };
350
354
  if (!Array.isArray(groups)) {
351
- throw new Error('hooks "PreToolUse" is not an array; not modifying it');
355
+ throw new Error(`hooks "${event}" is not an array; not modifying it`);
352
356
  }
353
357
  let changed = false;
354
358
  let found = false;
@@ -361,7 +365,7 @@ function placeHeavyHook(
361
365
  const kept: unknown[] = [];
362
366
  let groupChanged = false;
363
367
  for (const hook of group.hooks) {
364
- if (!isHeavyHookObject(hook)) {
368
+ if (!isMine(hook)) {
365
369
  kept.push(hook);
366
370
  } else if (desired !== null && !found) {
367
371
  found = true;
@@ -378,7 +382,7 @@ function placeHeavyHook(
378
382
  changed = true;
379
383
  if (kept.length > 0) next.push({ ...group, hooks: kept });
380
384
  }
381
- if (changed) hooks.PreToolUse = next;
385
+ if (changed) hooks[event] = next;
382
386
  return { changed, found };
383
387
  }
384
388
 
@@ -426,6 +430,80 @@ export async function syncJsonHeavyCommandHook(
426
430
  return result;
427
431
  }
428
432
 
433
+ /** The command CLEO's ask-enforce Stop hook runs (T13420); also its marker. */
434
+ export const ASK_ENFORCE_HOOK_ID = 'cleo hook ask-enforce';
435
+
436
+ /**
437
+ * CLEO's ask-enforce Stop hook object (T13420). The script prints the hook's
438
+ * answer only when it is a JSON object and always exits 0, so a machine
439
+ * without `cleo`, or an older `cleo` that lacks the subcommand, never blocks.
440
+ *
441
+ * @param provider - `claude-code` or `codex` (Codex runs hooks through the login shell).
442
+ */
443
+ export function askEnforceHookObject(provider: 'claude-code' | 'codex'): Record<string, unknown> {
444
+ const script = `command -v cleo >/dev/null 2>&1 || exit 0; out="$(${ASK_ENFORCE_HOOK_ID} --provider ${provider} 2>/dev/null)"; case "$out" in "{"*) printf '%s\\n' "$out";; esac; exit 0`;
445
+ const line = provider === 'codex' ? `/bin/sh -c ${shellWord(script)}` : script;
446
+ return {
447
+ type: 'command',
448
+ command: `${line} ${CLEO_HOOK_MARKER}`,
449
+ timeout: HEAVY_COMMAND_HOOK_TIMEOUT_SEC,
450
+ };
451
+ }
452
+
453
+ /** Whether one hook object is CLEO's ask-enforce Stop hook. */
454
+ export function isAskEnforceHookObject(hook: unknown): boolean {
455
+ return (
456
+ isPlainObject(hook) &&
457
+ typeof hook.command === 'string' &&
458
+ hook.command.includes(CLEO_HOOK_MARKER) &&
459
+ hook.command.includes(ASK_ENFORCE_HOOK_ID)
460
+ );
461
+ }
462
+
463
+ /**
464
+ * Add, refresh or (`install: false`) remove CLEO's ask-enforce Stop hook in a
465
+ * JSON hook config (`hooks.Stop`), through CAAMP's locked atomic writer. Only
466
+ * CLEO's own hook object is touched; a malformed file throws untouched.
467
+ *
468
+ * @param configPath - the config file (created when missing, unless removing).
469
+ * @param provider - `claude-code` or `codex`.
470
+ * @param install - `false` removes the hook.
471
+ * @returns what changed.
472
+ */
473
+ export async function syncJsonAskEnforceHook(
474
+ configPath: string,
475
+ provider: 'claude-code' | 'codex',
476
+ install: boolean,
477
+ ): Promise<HeavyHookSyncResult> {
478
+ const mine = (hook: unknown) => isAskEnforceHookObject(hook);
479
+ if (!install) {
480
+ if (!existsSync(configPath)) return 'unchanged';
481
+ const removed = await updateJsonConfigFile(configPath, (config) => {
482
+ if (config.hooks === undefined) return false;
483
+ return placeHeavyHook(hookMap(config), null, 'Stop', mine).changed;
484
+ });
485
+ return removed ? 'removed' : 'unchanged';
486
+ }
487
+ ensureParentDir(configPath);
488
+ let result: HeavyHookSyncResult = 'unchanged';
489
+ await updateJsonConfigFile(configPath, (config) => {
490
+ const hooks = hookMap(config);
491
+ const placed = placeHeavyHook(hooks, askEnforceHookObject(provider), 'Stop', mine);
492
+ if (placed.found) {
493
+ if (placed.changed) result = 'updated';
494
+ return placed.changed;
495
+ }
496
+ const existing = hooks.Stop;
497
+ hooks.Stop = [
498
+ ...(Array.isArray(existing) ? existing : []),
499
+ { hooks: [askEnforceHookObject(provider)] },
500
+ ];
501
+ result = 'installed';
502
+ return true;
503
+ });
504
+ return result;
505
+ }
506
+
429
507
  /** Claude Code's per-machine settings file, relative to the project. */
430
508
  export const CLAUDE_LOCAL_SETTINGS = '.claude/settings.local.json';
431
509