@cleocode/adapters 2026.10.3 → 2026.10.5-rc.ci.672

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.
Files changed (48) hide show
  1. package/dist/heavy-command-hook.d.ts +12 -0
  2. package/dist/heavy-command-hook.d.ts.map +1 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +2095 -1269
  6. package/dist/index.js.map +3 -3
  7. package/dist/providers/claude-code/adapter.d.ts.map +1 -1
  8. package/dist/providers/claude-code/hooks.d.ts +43 -17
  9. package/dist/providers/claude-code/hooks.d.ts.map +1 -1
  10. package/dist/providers/claude-code/install.d.ts +42 -14
  11. package/dist/providers/claude-code/install.d.ts.map +1 -1
  12. package/dist/providers/claude-code/paths.d.ts +77 -0
  13. package/dist/providers/claude-code/paths.d.ts.map +1 -1
  14. package/dist/providers/codex/install.d.ts.map +1 -1
  15. package/dist/providers/cursor/install.d.ts.map +1 -1
  16. package/dist/providers/gemini-cli/install.d.ts.map +1 -1
  17. package/dist/providers/kimi/install.d.ts.map +1 -1
  18. package/dist/providers/openai-sdk/install.d.ts.map +1 -1
  19. package/dist/providers/opencode/install.d.ts.map +1 -1
  20. package/dist/providers/pi/install.d.ts.map +1 -1
  21. package/dist/providers/shared/heavy-command-hook-delivery.d.ts +97 -0
  22. package/dist/providers/shared/heavy-command-hook-delivery.d.ts.map +1 -0
  23. package/dist/providers/shared/heavy-command-hook-install.d.ts +153 -7
  24. package/dist/providers/shared/heavy-command-hook-install.d.ts.map +1 -1
  25. package/dist/providers/shared/project-instruction-file.d.ts +30 -0
  26. package/dist/providers/shared/project-instruction-file.d.ts.map +1 -0
  27. package/package.json +9 -5
  28. package/src/__tests__/claude-code-adapter.test.ts +11 -6
  29. package/src/heavy-command-hook.ts +21 -0
  30. package/src/index.ts +12 -0
  31. package/src/providers/claude-code/__tests__/hooks-install.test.ts +20 -17
  32. package/src/providers/claude-code/__tests__/no-user-global-write.test.ts +293 -0
  33. package/src/providers/claude-code/adapter.ts +3 -1
  34. package/src/providers/claude-code/hooks.ts +138 -51
  35. package/src/providers/claude-code/install.ts +123 -52
  36. package/src/providers/claude-code/paths.ts +159 -1
  37. package/src/providers/codex/install.ts +7 -6
  38. package/src/providers/cursor/install.ts +26 -12
  39. package/src/providers/gemini-cli/install.ts +4 -2
  40. package/src/providers/kimi/install.ts +4 -2
  41. package/src/providers/openai-sdk/install.ts +5 -2
  42. package/src/providers/opencode/install.ts +4 -2
  43. package/src/providers/pi/install.ts +4 -2
  44. package/src/providers/shared/__tests__/heavy-command-hook-delivery.test.ts +580 -0
  45. package/src/providers/shared/__tests__/heavy-command-hook-install.test.ts +20 -0
  46. package/src/providers/shared/heavy-command-hook-delivery.ts +620 -0
  47. package/src/providers/shared/heavy-command-hook-install.ts +401 -32
  48. package/src/providers/shared/project-instruction-file.ts +47 -0
@@ -48,6 +48,30 @@ export declare const HEAVY_TOOL_WORDS: readonly string[];
48
48
  * home directory, whose provider configs are the user-global ones.
49
49
  */
50
50
  export type HeavyHookSyncResult = 'installed' | 'updated' | 'removed' | 'unchanged' | 'skipped';
51
+ /**
52
+ * A config path cannot be created because something on the way to it exists
53
+ * but is not a directory (T13124: a stray empty `<project>/.codex` file made
54
+ * `mkdirSync` throw `EEXIST` and aborted the Codex install). Nothing is
55
+ * written; the delivery reports the provider as `blocked`.
56
+ */
57
+ export declare class HeavyHookPathBlockedError extends Error {
58
+ /** The existing path that is not a directory. */
59
+ readonly path: string;
60
+ /**
61
+ * @param path - the existing non-directory path.
62
+ * @param target - the config file that would have lived under it.
63
+ */
64
+ constructor(path: string, target: string);
65
+ }
66
+ /**
67
+ * The nearest existing path at or above `dir` when it is NOT a directory, or
68
+ * `null` when the nearest existing one is a directory (so `mkdir -p dir`
69
+ * can succeed). Errors other than "does not exist" return `null` and are left
70
+ * to the write that follows.
71
+ *
72
+ * @param dir - the directory a config file needs.
73
+ */
74
+ export declare function nonDirectoryAncestor(dir: string): string | null;
51
75
  /**
52
76
  * Whether `projectDir` is the user's home directory. There,
53
77
  * `<home>/.claude/settings.local.json`, `<home>/.codex/hooks.json` and
@@ -108,6 +132,8 @@ export declare function heavyCommandHookObject(provider: 'claude-code' | 'codex'
108
132
  * @param provider - `claude-code` or `codex`.
109
133
  */
110
134
  export declare function heavyCommandHookEntry(provider: 'claude-code' | 'codex'): Record<string, unknown>;
135
+ /** Whether one hook object is CLEO's heavy-command hook. */
136
+ export declare function isHeavyHookObject(hook: unknown): boolean;
111
137
  /**
112
138
  * Add, refresh or (mode `off`) remove CLEO's heavy-command hook in a JSON hook
113
139
  * config, through CAAMP's locked atomic writer. Only CLEO's own hook object is
@@ -119,28 +145,147 @@ export declare function heavyCommandHookEntry(provider: 'claude-code' | 'codex')
119
145
  * @returns what changed.
120
146
  */
121
147
  export declare function syncJsonHeavyCommandHook(configPath: string, provider: 'claude-code' | 'codex', mode: HeavyCommandHookMode): Promise<HeavyHookSyncResult>;
122
- /** First line of the `info/exclude` block CLEO adds (and alone may remove). */
148
+ /** Claude Code's per-machine settings file, relative to the project. */
149
+ export declare const CLAUDE_LOCAL_SETTINGS = ".claude/settings.local.json";
150
+ /** First line of the `info/exclude` block CLEO adds for `settings.local.json` (and alone may remove). */
123
151
  export declare const LOCAL_SETTINGS_EXCLUDE_MARKER = "# cleo-hook: keep the per-machine heavy-command hook settings out of git (T12983)";
124
152
  /**
125
- * Keep `.claude/settings.local.json` out of git when nothing ignores it yet
126
- * (Claude Code ignores the file only when it creates it itself). Adds a marked
127
- * block naming this project's file to the repository's `info/exclude`, never
128
- * to a tracked `.gitignore`. Keyed on the exact path line, so a second CLEO
153
+ * First line of the `info/exclude` block CLEO adds for any other hook file it
154
+ * writes: Codex's `.codex/hooks.json` and the opencode plugin (T13124, gh#1805:
155
+ * an untracked plugin file widened `cleo verify`'s evidence scope).
156
+ */
157
+ export declare const HOOK_FILE_EXCLUDE_MARKER = "# cleo-hook: keep a per-machine heavy-command hook file out of git (T13124)";
158
+ /** Codex's project hook config, relative to the project. */
159
+ export declare const CODEX_HOOKS_FILE = ".codex/hooks.json";
160
+ /** The opencode plugin, relative to the project. */
161
+ export declare const OPENCODE_PLUGIN_FILE = ".opencode/plugins/cleo-heavy-command.js";
162
+ /**
163
+ * Keep a hook file CLEO wrote out of git when nothing ignores it yet: add a
164
+ * marked block naming this project's file to the repository's `info/exclude`,
165
+ * never to a tracked `.gitignore`. A file git already tracks is left alone (an
166
+ * exclude cannot untrack it). Keyed on the exact path line, so a second CLEO
129
167
  * project in a subdirectory of the same repository gets its own line.
130
168
  * Idempotent; does nothing outside a git work tree.
131
169
  *
132
170
  * @param projectDir - the project root.
171
+ * @param relPath - the file, relative to the project (forward slashes).
172
+ * @param marker - the block's first line.
173
+ * @returns whether a block was added.
174
+ */
175
+ export declare function excludeHookFileFromGit(projectDir: string, relPath: string, marker?: string): boolean;
176
+ /**
177
+ * Remove the block {@link excludeHookFileFromGit} added for this project's
178
+ * file, and only that: the exact path line and the CLEO marker right above it.
179
+ *
180
+ * @param projectDir - the project root.
181
+ * @param relPath - the file, relative to the project (forward slashes).
182
+ * @returns whether a block was removed.
183
+ */
184
+ export declare function unexcludeHookFileFromGit(projectDir: string, relPath: string): boolean;
185
+ /**
186
+ * Whether git sees a hook file as an untracked change: inside a work tree,
187
+ * not ignored and not tracked. Such a file shows up in `git status` and
188
+ * widens `cleo verify`'s evidence scope (gh#1805).
189
+ *
190
+ * @param projectDir - the project root.
191
+ * @param relPath - the file, relative to the project (forward slashes).
192
+ */
193
+ export declare function hookFileVisibleToGit(projectDir: string, relPath: string): boolean;
194
+ /**
195
+ * Keep `.claude/settings.local.json` out of git when nothing ignores it yet
196
+ * (Claude Code ignores the file only when it creates it itself). See
197
+ * {@link excludeHookFileFromGit}.
198
+ *
199
+ * @param projectDir - the project root.
133
200
  * @returns whether a block was added.
134
201
  */
135
202
  export declare function excludeLocalSettingsFromGit(projectDir: string): boolean;
136
203
  /**
137
204
  * Remove the block {@link excludeLocalSettingsFromGit} added for this
138
- * project, and only that: the exact path line and the marker right above it.
205
+ * project, and only that.
139
206
  *
140
207
  * @param projectDir - the project root.
141
208
  * @returns whether a block was removed.
142
209
  */
143
210
  export declare function unexcludeLocalSettingsFromGit(projectDir: string): boolean;
211
+ /**
212
+ * A hook config CLEO will not add its per-machine hook to, because it is the
213
+ * project's own: Codex's `.codex/hooks.json` is committable, unlike Claude
214
+ * Code's `settings.local.json`. Writing there would put a per-machine hook
215
+ * into a team file, and excluding it would hide the team's file from git.
216
+ * Nothing is written; the delivery reports `needs-consent` with the entry to
217
+ * add by hand.
218
+ */
219
+ export declare class HeavyHookSharedConfigError extends Error {
220
+ /** The project's own config file. */
221
+ readonly path: string;
222
+ /**
223
+ * @param path - the config file.
224
+ * @param why - what makes it the project's own.
225
+ */
226
+ constructor(path: string, why: string);
227
+ }
228
+ /**
229
+ * Why Codex's `.codex/hooks.json` in `projectDir` is the project's own config
230
+ * that CLEO must not write or hide, or `null` when CLEO may: the file is
231
+ * absent, or is one CLEO created and git does not track (review MED-3). An
232
+ * unreadable file is left to the sync, which refuses it.
233
+ *
234
+ * @param projectDir - the project root.
235
+ * @param checkGit - also ask git whether the file is tracked (one spawn);
236
+ * `false` reads the file only (the session briefing).
237
+ */
238
+ export declare function codexHooksSharedReason(projectDir: string, checkGit?: boolean): string | null;
239
+ /**
240
+ * Whether the committed (`HEAD`) version of Codex's `.codex/hooks.json` holds
241
+ * CLEO's hook: the team adopted it, so mode `off` on one machine must not
242
+ * remove it from the tracked file (review MED-3b).
243
+ *
244
+ * @param projectDir - the project root.
245
+ */
246
+ export declare function committedCodexHookHoldsCleo(projectDir: string): boolean;
247
+ /**
248
+ * Mode `off` on a TRACKED `hooks.json` whose only change from `HEAD` is
249
+ * CLEO's hook: write `HEAD`'s exact bytes back, so the file returns to its
250
+ * committed state instead of being re-serialised with CLEO's formatting
251
+ * (review LOW-3). Returns whether it did.
252
+ *
253
+ * @param projectDir - the project root.
254
+ */
255
+ export declare function restoreCommittedCodexHooks(projectDir: string): boolean;
256
+ /**
257
+ * Whether CLEO's own marked `info/exclude` block names this project file
258
+ * (it hides the file from git).
259
+ *
260
+ * @param projectDir - the project root.
261
+ * @param relPath - the file, relative to the project (forward slashes).
262
+ */
263
+ export declare function excludedByCleo(projectDir: string, relPath: string): boolean;
264
+ /**
265
+ * The exact `PreToolUse` entry to add to Codex's `.codex/hooks.json` by hand,
266
+ * for a project whose own config CLEO does not write (`needs-consent`).
267
+ */
268
+ export declare function codexHookSnippet(): string;
269
+ /**
270
+ * Whether git tracks a project file and it has uncommitted changes.
271
+ *
272
+ * @param projectDir - the project root.
273
+ * @param relPath - the file, relative to the project (forward slashes).
274
+ */
275
+ export declare function trackedFileModified(projectDir: string, relPath: string): boolean;
276
+ /**
277
+ * Codex: sync the hook in `<project>/.codex/hooks.json` and keep that file out
278
+ * of git (T13124), as {@link syncClaudeCodeHeavyCommandHook} does for Claude
279
+ * Code's settings. A shared `hooks.json` (tracked, or holding hooks that are
280
+ * not CLEO's) is never written: {@link HeavyHookSharedConfigError}. Mode `off`
281
+ * still removes CLEO's own hook object from it.
282
+ *
283
+ * @param projectDir - the project root.
284
+ * @param mode - the resolved hook mode.
285
+ * @returns what changed in `hooks.json`.
286
+ * @throws {HeavyHookSharedConfigError} when `hooks.json` is shared.
287
+ */
288
+ export declare function syncCodexHeavyCommandHook(projectDir: string, mode: HeavyCommandHookMode): Promise<HeavyHookSyncResult>;
144
289
  /**
145
290
  * Claude Code: sync the hook in `<project>/.claude/settings.local.json`, keep
146
291
  * that file out of git, and remove a hook an earlier build put in the shared
@@ -163,7 +308,8 @@ export declare function syncClaudeCodeHeavyCommandHook(projectDir: string, mode:
163
308
  export declare function opencodeHeavyCommandPluginSource(): string;
164
309
  /**
165
310
  * Write (or, mode `off`, delete) the opencode plugin in
166
- * `<projectDir>/.opencode/plugins/`. Rewritten only when its content changed.
311
+ * `<projectDir>/.opencode/plugins/`, and keep it out of git (T13124).
312
+ * Rewritten only when its content changed.
167
313
  *
168
314
  * @param projectDir - the project root.
169
315
  * @param mode - the resolved hook mode.
@@ -1 +1 @@
1
- {"version":3,"file":"heavy-command-hook-install.d.ts","sourceRoot":"","sources":["../../../src/providers/shared/heavy-command-hook-install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAeH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGhE,oEAAoE;AACpE,eAAO,MAAM,qBAAqB,4BAA4B,CAAC;AAE/D,iFAAiF;AACjF,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAEjD,kDAAkD;AAClD,eAAO,MAAM,6BAA6B,0BAA0B,CAAC;AAErE;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EA+B5C,CAAC;AAEH;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;AAUhG;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,OAAO,CAEnF;AAED;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,QAOjC,CAAC;AAEH,mFAAmF;AACnF,eAAO,MAAM,wBAAwB,2BAA2B,CAAC;AAEjE;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,GAAE,MAAgD,GACpD,MAAM,CAkBR;AAqDD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,GAAG,MAAM,CAIjF;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMjG;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEhG;AA4DD;;;;;;;;;GASG;AACH,wBAAsB,wBAAwB,CAC5C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,aAAa,GAAG,OAAO,EACjC,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,mBAAmB,CAAC,CA4B9B;AAKD,+EAA+E;AAC/E,eAAO,MAAM,6BAA6B,sFAC2C,CAAC;AA6BtF;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAWvE;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CASzE;AAED;;;;;;;;GAQG;AACH,wBAAsB,8BAA8B,CAClD,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,mBAAmB,CAAC,CAc9B;AAED;;;;;;;;GAQG;AACH,wBAAgB,gCAAgC,IAAI,MAAM,CAmDzD;AAED;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,oBAAoB,GACzB,mBAAmB,CAarB"}
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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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * A provider's PROJECT instruction file, skipped (not failed) at a project
3
+ * rooted at the home directory (T13227).
4
+ *
5
+ * Providers load CLAUDE.md / AGENTS.md / GEMINI.md from the working directory
6
+ * up through every ancestor, so a file at `$HOME` reaches every session under
7
+ * it: CAAMP refuses it with `HomeInstructionFileError`. An adapter's install
8
+ * reports that step as `skipped` and carries on with the rest, the way the
9
+ * heavy-command hook already skips the home directory (T12983).
10
+ *
11
+ * @task T13227
12
+ */
13
+ import { type EnsureProviderInstructionFileOptions, type EnsureProviderInstructionFileResult } from '@cleocode/caamp';
14
+ /**
15
+ * Ensure a provider's project instruction file, or `null` when the project is
16
+ * the home directory and the file was refused (nothing written).
17
+ *
18
+ * @param providerId - CAAMP provider id.
19
+ * @param projectDir - the project root.
20
+ * @param options - references, content, scope.
21
+ * @returns the CAAMP result, or `null` when skipped at `$HOME`.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const r = await ensureProjectInstructionFile('codex', projectDir, { scope: 'project' });
26
+ * if (r === null) details.instructionFile = 'skipped';
27
+ * ```
28
+ */
29
+ export declare function ensureProjectInstructionFile(providerId: string, projectDir: string, options: EnsureProviderInstructionFileOptions): Promise<EnsureProviderInstructionFileResult | null>;
30
+ //# sourceMappingURL=project-instruction-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-instruction-file.d.ts","sourceRoot":"","sources":["../../../src/providers/shared/project-instruction-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,KAAK,oCAAoC,EACzC,KAAK,mCAAmC,EAGzC,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,4BAA4B,CAChD,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,oCAAoC,GAC5C,OAAO,CAAC,mCAAmC,GAAG,IAAI,CAAC,CAOrD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/adapters",
3
- "version": "2026.10.3",
3
+ "version": "2026.10.5-rc.ci.672",
4
4
  "description": "Unified provider adapters for CLEO (Claude Code, OpenCode, Cursor)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -9,15 +9,19 @@
9
9
  ".": {
10
10
  "types": "./dist/index.d.ts",
11
11
  "import": "./dist/index.js"
12
+ },
13
+ "./heavy-command-hook": {
14
+ "types": "./dist/heavy-command-hook.d.ts",
15
+ "import": "./dist/heavy-command-hook.js"
12
16
  }
13
17
  },
14
18
  "dependencies": {
15
19
  "@ai-sdk/anthropic": "^3.0.69",
16
20
  "@ai-sdk/openai": "^2.0.53",
17
21
  "ai": "^6.0.168",
18
- "@cleocode/caamp": "2026.10.3",
19
- "@cleocode/contracts": "2026.10.3",
20
- "@cleocode/paths": "2026.10.3"
22
+ "@cleocode/caamp": "2026.10.5-rc.ci.672",
23
+ "@cleocode/contracts": "2026.10.5-rc.ci.672",
24
+ "@cleocode/paths": "2026.10.5-rc.ci.672"
21
25
  },
22
26
  "license": "MIT",
23
27
  "engines": {
@@ -30,7 +34,7 @@
30
34
  "devDependencies": {
31
35
  "@types/node": "^24.3.0",
32
36
  "vitest": "^4.1.4",
33
- "@cleocode/playbooks": "2026.10.3"
37
+ "@cleocode/playbooks": "2026.10.5-rc.ci.672"
34
38
  },
35
39
  "repository": {
36
40
  "type": "git",
@@ -7,7 +7,7 @@
7
7
  * @task T5240
8
8
  */
9
9
 
10
- import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
10
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
11
11
  import { tmpdir } from 'node:os';
12
12
  import { join } from 'node:path';
13
13
  import {
@@ -108,11 +108,16 @@ describe('ClaudeCodeHookProvider — integration', () => {
108
108
  });
109
109
 
110
110
  it('tracks registration state', async () => {
111
- expect(hooks.isRegistered()).toBe(false);
112
- await hooks.registerNativeHooks('/tmp/test');
113
- expect(hooks.isRegistered()).toBe(true);
114
- await hooks.unregisterNativeHooks();
115
- expect(hooks.isRegistered()).toBe(false);
111
+ const projectDir = mkdtempSync(join(tmpdir(), 'cleo-claude-hooks-'));
112
+ try {
113
+ expect(hooks.isRegistered()).toBe(false);
114
+ await hooks.registerNativeHooks(projectDir);
115
+ expect(hooks.isRegistered()).toBe(true);
116
+ await hooks.unregisterNativeHooks();
117
+ expect(hooks.isRegistered()).toBe(false);
118
+ } finally {
119
+ rmSync(projectDir, { recursive: true, force: true });
120
+ }
116
121
  });
117
122
 
118
123
  it('exposes event map for introspection', () => {
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `@cleocode/adapters/heavy-command-hook`: project-level delivery of CLEO's
3
+ * heavy-command hook (T13124). A light entry point: `@cleocode/core` loads it
4
+ * at run time for `cleo init`, `cleo upgrade`, `cleo doctor` and the session
5
+ * briefing without pulling in every provider adapter.
6
+ *
7
+ * @packageDocumentation
8
+ * @task T13124
9
+ */
10
+
11
+ export type { HeavyHookDetection } from './providers/shared/heavy-command-hook-delivery.js';
12
+ export {
13
+ detectHeavyHookProvider,
14
+ HEAVY_HOOK_DELIVERY_PROVIDERS,
15
+ HEAVY_HOOK_FIX_COMMAND,
16
+ heavyHookDeliveryApi,
17
+ heavyHookTarget,
18
+ inspectProjectHeavyCommandHooks,
19
+ probeHeavyHookCli,
20
+ syncProjectHeavyCommandHooks,
21
+ } from './providers/shared/heavy-command-hook-delivery.js';
package/src/index.ts CHANGED
@@ -87,6 +87,18 @@ export {
87
87
  OpenCodeInstallProvider,
88
88
  OpenCodeSpawnProvider,
89
89
  } from './providers/opencode/index.js';
90
+ // T13124 — project-level heavy-command hook delivery (also `@cleocode/adapters/heavy-command-hook`).
91
+ export type { HeavyHookDetection } from './providers/shared/heavy-command-hook-delivery.js';
92
+ export {
93
+ detectHeavyHookProvider,
94
+ HEAVY_HOOK_DELIVERY_PROVIDERS,
95
+ HEAVY_HOOK_FIX_COMMAND,
96
+ heavyHookDeliveryApi,
97
+ heavyHookTarget,
98
+ inspectProjectHeavyCommandHooks,
99
+ probeHeavyHookCli,
100
+ syncProjectHeavyCommandHooks,
101
+ } from './providers/shared/heavy-command-hook-delivery.js';
90
102
  export type {
91
103
  HookTemplateProviderId,
92
104
  InstallHookTemplatesOptions,
@@ -3,9 +3,10 @@
3
3
  *
4
4
  * Validates that:
5
5
  * - Shared `cleo-precompact-core.sh` and provider-specific
6
- * `precompact-safestop.sh` are copied into `~/.claude/hooks/`.
7
- * - `~/.claude/settings.json` gains a `PreCompact` entry pointing at the
8
- * installed shim, tagged with the `# cleo-hook` sentinel for clean uninstall.
6
+ * `precompact-safestop.sh` are copied into the PROJECT's `.claude/hooks/`.
7
+ * - The project's `.claude/settings.local.json` gains a `PreCompact` entry
8
+ * pointing at the installed shim, tagged with the `# cleo-hook` sentinel.
9
+ * - The user-global `$CLAUDE_HOME` is never written (T13227).
9
10
  * - Repeat invocations are idempotent (no duplicate settings entries).
10
11
  * - The source templates contain the universal CLEO CLI invocations so the
11
12
  * bash contract remains DRY across providers.
@@ -14,6 +15,7 @@
14
15
  * impersonates `$HOME` via the `HOME` env var, so no user config is touched.
15
16
  *
16
17
  * @task T1013
18
+ * @task T13227
17
19
  * @epic T1000
18
20
  */
19
21
 
@@ -67,7 +69,7 @@ describe('ClaudeCodeInstallProvider — PreCompact hook templates', () => {
67
69
  rmSync(projectDir, { recursive: true, force: true });
68
70
  });
69
71
 
70
- it('installs both bash templates into $HOME/.claude/hooks/', async () => {
72
+ it('installs both bash templates into the project .claude/hooks/', async () => {
71
73
  const provider = new ClaudeCodeInstallProvider();
72
74
 
73
75
  const result = await provider.install({ projectDir });
@@ -79,20 +81,21 @@ describe('ClaudeCodeInstallProvider — PreCompact hook templates', () => {
79
81
  } | null;
80
82
 
81
83
  expect(hookTemplates).not.toBeNull();
82
- expect(hookTemplates?.templates.targetDir).toBe(join(fakeHome, '.claude', 'hooks'));
84
+ expect(hookTemplates?.templates.targetDir).toBe(join(projectDir, '.claude', 'hooks'));
85
+ expect(existsSync(join(fakeHome, '.claude'))).toBe(false);
83
86
  const installed = hookTemplates?.templates.installedFiles ?? [];
84
87
  expect(installed.some((p) => p.endsWith('cleo-precompact-core.sh'))).toBe(true);
85
88
  expect(installed.some((p) => p.endsWith('precompact-safestop.sh'))).toBe(true);
86
89
 
87
90
  // Installed shim sources the shared helper.
88
91
  const shim = readFileSync(
89
- join(fakeHome, '.claude', 'hooks', 'precompact-safestop.sh'),
92
+ join(projectDir, '.claude', 'hooks', 'precompact-safestop.sh'),
90
93
  'utf-8',
91
94
  );
92
95
  expect(shim).toContain('cleo-precompact-core.sh');
93
96
  // Shared helper invokes the universal CLEO CLI.
94
97
  const core = readFileSync(
95
- join(fakeHome, '.claude', 'hooks', 'cleo-precompact-core.sh'),
98
+ join(projectDir, '.claude', 'hooks', 'cleo-precompact-core.sh'),
96
99
  'utf-8',
97
100
  );
98
101
  expect(core).toContain('cleo memory precompact-flush');
@@ -100,11 +103,11 @@ describe('ClaudeCodeInstallProvider — PreCompact hook templates', () => {
100
103
  expect(core).toMatch(/cleo_cmd.*safestop/);
101
104
  });
102
105
 
103
- it('writes a PreCompact entry into $HOME/.claude/settings.json tagged # cleo-hook', async () => {
106
+ it('writes a PreCompact entry into the project settings.local.json tagged # cleo-hook', async () => {
104
107
  const provider = new ClaudeCodeInstallProvider();
105
108
  await provider.install({ projectDir });
106
109
 
107
- const settingsPath = join(fakeHome, '.claude', 'settings.json');
110
+ const settingsPath = join(projectDir, '.claude', 'settings.local.json');
108
111
  const settings = JSON.parse(readFileSync(settingsPath, 'utf-8')) as {
109
112
  hooks?: Record<string, Array<{ hooks?: Array<{ command?: string; type?: string }> }>>;
110
113
  };
@@ -124,7 +127,7 @@ describe('ClaudeCodeInstallProvider — PreCompact hook templates', () => {
124
127
  await provider.install({ projectDir });
125
128
  await provider.install({ projectDir });
126
129
 
127
- const settingsPath = join(fakeHome, '.claude', 'settings.json');
130
+ const settingsPath = join(projectDir, '.claude', 'settings.local.json');
128
131
  const settings = JSON.parse(readFileSync(settingsPath, 'utf-8')) as {
129
132
  hooks?: { PreCompact?: unknown[] };
130
133
  };
@@ -134,12 +137,12 @@ describe('ClaudeCodeInstallProvider — PreCompact hook templates', () => {
134
137
  // T12385 — a malformed settings.json used to be replaced with an object
135
138
  // holding only CLEO's entries (the parse error was caught and the writer
136
139
  // "started fresh"). It must now be reported and left byte-identical.
137
- describe('T12385 — malformed settings.json is never rewritten', () => {
140
+ describe('T12385 — malformed settings file is never rewritten', () => {
138
141
  const malformed = '{\n "permissions": { "allow": ["Bash(ls)"] },\n "model": "opus",\n';
139
142
 
140
143
  it('install reports the parse error and leaves the file byte-identical', async () => {
141
- const settingsPath = join(fakeHome, '.claude', 'settings.json');
142
- mkdirSync(join(fakeHome, '.claude'), { recursive: true });
144
+ const settingsPath = join(projectDir, '.claude', 'settings.local.json');
145
+ mkdirSync(join(projectDir, '.claude'), { recursive: true });
143
146
  writeFileSync(settingsPath, malformed, 'utf-8');
144
147
 
145
148
  const result = await new ClaudeCodeInstallProvider().install({ projectDir });
@@ -153,8 +156,8 @@ describe('ClaudeCodeInstallProvider — PreCompact hook templates', () => {
153
156
  });
154
157
 
155
158
  it('hook registration reports the parse error and leaves the file byte-identical', async () => {
156
- const settingsPath = join(fakeHome, '.claude', 'settings.json');
157
- mkdirSync(join(fakeHome, '.claude'), { recursive: true });
159
+ const settingsPath = join(projectDir, '.claude', 'settings.local.json');
160
+ mkdirSync(join(projectDir, '.claude'), { recursive: true });
158
161
  writeFileSync(settingsPath, malformed, 'utf-8');
159
162
  const stderr = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
160
163
 
@@ -168,8 +171,8 @@ describe('ClaudeCodeInstallProvider — PreCompact hook templates', () => {
168
171
  });
169
172
 
170
173
  it('preserves unrelated user settings when it does write', async () => {
171
- const settingsPath = join(fakeHome, '.claude', 'settings.json');
172
- mkdirSync(join(fakeHome, '.claude'), { recursive: true });
174
+ const settingsPath = join(projectDir, '.claude', 'settings.local.json');
175
+ mkdirSync(join(projectDir, '.claude'), { recursive: true });
173
176
  writeFileSync(
174
177
  settingsPath,
175
178
  JSON.stringify({ model: 'opus', hooks: { Stop: [{ matcher: 'x', hooks: [] }] } }),