@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.
- package/dist/heavy-command-hook.d.ts +12 -0
- package/dist/heavy-command-hook.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2095 -1269
- package/dist/index.js.map +3 -3
- package/dist/providers/claude-code/adapter.d.ts.map +1 -1
- package/dist/providers/claude-code/hooks.d.ts +43 -17
- package/dist/providers/claude-code/hooks.d.ts.map +1 -1
- package/dist/providers/claude-code/install.d.ts +42 -14
- package/dist/providers/claude-code/install.d.ts.map +1 -1
- package/dist/providers/claude-code/paths.d.ts +77 -0
- package/dist/providers/claude-code/paths.d.ts.map +1 -1
- package/dist/providers/codex/install.d.ts.map +1 -1
- package/dist/providers/cursor/install.d.ts.map +1 -1
- package/dist/providers/gemini-cli/install.d.ts.map +1 -1
- package/dist/providers/kimi/install.d.ts.map +1 -1
- package/dist/providers/openai-sdk/install.d.ts.map +1 -1
- package/dist/providers/opencode/install.d.ts.map +1 -1
- package/dist/providers/pi/install.d.ts.map +1 -1
- package/dist/providers/shared/heavy-command-hook-delivery.d.ts +97 -0
- package/dist/providers/shared/heavy-command-hook-delivery.d.ts.map +1 -0
- package/dist/providers/shared/heavy-command-hook-install.d.ts +153 -7
- package/dist/providers/shared/heavy-command-hook-install.d.ts.map +1 -1
- package/dist/providers/shared/project-instruction-file.d.ts +30 -0
- package/dist/providers/shared/project-instruction-file.d.ts.map +1 -0
- package/package.json +9 -5
- package/src/__tests__/claude-code-adapter.test.ts +11 -6
- package/src/heavy-command-hook.ts +21 -0
- package/src/index.ts +12 -0
- package/src/providers/claude-code/__tests__/hooks-install.test.ts +20 -17
- package/src/providers/claude-code/__tests__/no-user-global-write.test.ts +293 -0
- package/src/providers/claude-code/adapter.ts +3 -1
- package/src/providers/claude-code/hooks.ts +138 -51
- package/src/providers/claude-code/install.ts +123 -52
- package/src/providers/claude-code/paths.ts +159 -1
- package/src/providers/codex/install.ts +7 -6
- package/src/providers/cursor/install.ts +26 -12
- package/src/providers/gemini-cli/install.ts +4 -2
- package/src/providers/kimi/install.ts +4 -2
- package/src/providers/openai-sdk/install.ts +5 -2
- package/src/providers/opencode/install.ts +4 -2
- package/src/providers/pi/install.ts +4 -2
- package/src/providers/shared/__tests__/heavy-command-hook-delivery.test.ts +580 -0
- package/src/providers/shared/__tests__/heavy-command-hook-install.test.ts +20 -0
- package/src/providers/shared/heavy-command-hook-delivery.ts +620 -0
- package/src/providers/shared/heavy-command-hook-install.ts +401 -32
- 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
|
-
/**
|
|
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
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
|
|
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
|
|
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
|
|
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;
|
|
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
|
+
"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.
|
|
19
|
-
"@cleocode/contracts": "2026.10.
|
|
20
|
-
"@cleocode/paths": "2026.10.
|
|
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.
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
7
|
-
* -
|
|
8
|
-
* installed shim, tagged with the `# cleo-hook` sentinel
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
142
|
-
mkdirSync(join(
|
|
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(
|
|
157
|
-
mkdirSync(join(
|
|
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(
|
|
172
|
-
mkdirSync(join(
|
|
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: [] }] } }),
|