@cleocode/adapters 2026.4.101 → 2026.4.102

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 (28) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1221 -16720
  4. package/dist/index.js.map +4 -4
  5. package/dist/providers/claude-code/install.d.ts +36 -0
  6. package/dist/providers/claude-code/install.d.ts.map +1 -1
  7. package/dist/providers/cursor/install.d.ts +36 -0
  8. package/dist/providers/cursor/install.d.ts.map +1 -1
  9. package/dist/providers/opencode/install.d.ts +47 -2
  10. package/dist/providers/opencode/install.d.ts.map +1 -1
  11. package/dist/providers/shared/hook-template-installer.d.ts +109 -0
  12. package/dist/providers/shared/hook-template-installer.d.ts.map +1 -0
  13. package/package.json +4 -4
  14. package/src/index.ts +11 -0
  15. package/src/providers/README.md +137 -0
  16. package/src/providers/claude-code/__tests__/hooks-install.test.ts +113 -0
  17. package/src/providers/claude-code/install.ts +129 -0
  18. package/src/providers/claude-code/templates/hooks/precompact-safestop.sh +52 -0
  19. package/src/providers/cursor/__tests__/hooks-install.test.ts +88 -0
  20. package/src/providers/cursor/install.ts +117 -0
  21. package/src/providers/cursor/templates/hooks/precompact.sh +47 -0
  22. package/src/providers/gemini-cli/templates/hooks/precompact.sh +47 -0
  23. package/src/providers/opencode/__tests__/hooks-install.test.ts +87 -0
  24. package/src/providers/opencode/install.ts +134 -3
  25. package/src/providers/opencode/templates/hooks/precompact.sh +42 -0
  26. package/src/providers/pi/templates/hooks/README.md +40 -0
  27. package/src/providers/shared/hook-template-installer.ts +268 -0
  28. package/src/providers/shared/templates/hooks/cleo-precompact-core.sh +128 -0
@@ -74,5 +74,41 @@ export declare class ClaudeCodeInstallProvider implements AdapterInstallProvider
74
74
  * @returns Description of what was registered, or null if no change needed
75
75
  */
76
76
  private registerPlugin;
77
+ /**
78
+ * Install the CLEO PreCompact hook templates for Claude Code (T1013).
79
+ *
80
+ * Writes two files to `~/.claude/hooks/`:
81
+ * 1. `cleo-precompact-core.sh` — universal CLEO safestop helper (shared
82
+ * across all providers; sourced by the provider-specific shim).
83
+ * 2. `precompact-safestop.sh` — Claude-Code-flavoured wrapper that invokes
84
+ * `cleo memory precompact-flush` and `cleo safestop`.
85
+ *
86
+ * Also registers a `PreCompact` entry in `~/.claude/settings.json` so Claude
87
+ * Code runs the hook when auto-compact fires (at 95% context).
88
+ *
89
+ * Idempotent: subsequent installs skip unchanged files and do not duplicate
90
+ * the settings.json hook entry.
91
+ *
92
+ * @returns Install summary (paths written + config change description), or
93
+ * `null` when no change was required.
94
+ *
95
+ * @task T1013
96
+ */
97
+ private installHookTemplates;
98
+ /**
99
+ * Register the PreCompact hook command in `~/.claude/settings.json`.
100
+ *
101
+ * The Claude Code native event name for the canonical `PreCompact` event is
102
+ * `PreCompact` (identity mapping — see `hook-mappings.json`). The entry is
103
+ * tagged with `# cleo-hook` so the uninstall flow can identify and remove
104
+ * our additions without touching user-authored hooks.
105
+ *
106
+ * @param shimPath - Absolute path to the installed `precompact-safestop.sh`.
107
+ * @returns `true` when a new hook entry was written, `false` when an
108
+ * equivalent entry was already present.
109
+ *
110
+ * @task T1013
111
+ */
112
+ private registerPreCompactHook;
77
113
  }
78
114
  //# sourceMappingURL=install.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/providers/claude-code/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAaH,OAAO,KAAK,EAAE,sBAAsB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAoBjG;;;;;;;;;;;;;GAaG;AACH,qBAAa,yBAA0B,YAAW,sBAAsB;IACtE;;;;;OAKG;IACG,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAgC9D;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAEhC;;;;OAIG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAkBrC;;;;;;OAMG;IACG,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpE;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IA+B7B;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IAsBvB;;;;OAIG;IACH,OAAO,CAAC,cAAc;CAiCvB"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/providers/claude-code/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAaH,OAAO,KAAK,EAAE,sBAAsB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAwBjG;;;;;;;;;;;;;GAaG;AACH,qBAAa,yBAA0B,YAAW,sBAAsB;IACtE;;;;;OAKG;IACG,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAuC9D;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAEhC;;;;OAIG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAkBrC;;;;;;OAMG;IACG,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpE;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IA+B7B;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IAsBvB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAkCtB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,oBAAoB;IAgC5B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,sBAAsB;CAmD/B"}
@@ -4,6 +4,7 @@
4
4
  * Handles CLEO installation into Cursor environments:
5
5
  * - Ensures .cursorrules has CLEO @-references (legacy format)
6
6
  * - Creates .cursor/rules/cleo.mdc with CLEO references (modern format)
7
+ * - Installs PreCompact hook shell shims + wires them into .cursor/hooks.json (T1013)
7
8
  *
8
9
  * Cursor supports two instruction file formats:
9
10
  * 1. Legacy: .cursorrules (flat file, project root)
@@ -12,6 +13,7 @@
12
13
  * This provider writes to both for maximum compatibility.
13
14
  *
14
15
  * @task T5240
16
+ * @task T1013
15
17
  */
16
18
  import type { AdapterInstallProvider, InstallOptions, InstallResult } from '@cleocode/contracts';
17
19
  /**
@@ -20,6 +22,7 @@ import type { AdapterInstallProvider, InstallOptions, InstallResult } from '@cle
20
22
  * Manages CLEO's integration with Cursor by:
21
23
  * 1. Creating/updating .cursorrules with @-references (legacy)
22
24
  * 2. Creating .cursor/rules/cleo.mdc with @-references (modern)
25
+ * 3. Installing the PreCompact hook shim + registering it in .cursor/hooks.json (T1013)
23
26
  *
24
27
  * @remarks
25
28
  * Installation is idempotent and writes to both instruction file formats
@@ -83,5 +86,38 @@ export declare class CursorInstallProvider implements AdapterInstallProvider {
83
86
  * Get list of instruction files that were updated.
84
87
  */
85
88
  private getUpdatedFileList;
89
+ /**
90
+ * Install the CLEO PreCompact hook templates for Cursor (T1013).
91
+ *
92
+ * Writes two files to `<projectDir>/.cursor/hooks/`:
93
+ * 1. `cleo-precompact-core.sh` — universal CLEO safestop helper.
94
+ * 2. `precompact.sh` — Cursor-flavoured wrapper.
95
+ *
96
+ * Also registers a `preCompact` entry in `.cursor/hooks.json`. The native
97
+ * event name `preCompact` comes from CAAMP's `hook-mappings.json` SSoT.
98
+ *
99
+ * Idempotent: re-running install skips unchanged files and avoids
100
+ * duplicating the hooks.json entry.
101
+ *
102
+ * @param projectDir - Project root directory.
103
+ * @returns Install summary, or `null` when no change was required.
104
+ *
105
+ * @task T1013
106
+ */
107
+ private installHookTemplates;
108
+ /**
109
+ * Register the PreCompact hook command in `.cursor/hooks.json`.
110
+ *
111
+ * Cursor's native event name for the canonical `PreCompact` is `preCompact`
112
+ * (camelCase — see CAAMP `hook-mappings.json`). Entries are tagged with a
113
+ * `# cleo-hook` comment so they can be cleanly removed on uninstall.
114
+ *
115
+ * @param projectDir - Project root directory.
116
+ * @param shimPath - Absolute path to the installed `precompact.sh`.
117
+ * @returns `true` when a new entry was written, `false` when already wired.
118
+ *
119
+ * @task T1013
120
+ */
121
+ private registerPreCompactHook;
86
122
  }
87
123
  //# sourceMappingURL=install.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/providers/cursor/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,sBAAsB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAajG;;;;;;;;;;;;GAYG;AACH,qBAAa,qBAAsB,YAAW,sBAAsB;IAClE;;;;;OAKG;IACG,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAoB9D;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAEhC;;;;OAIG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAqBrC;;;;;;OAMG;IACG,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpE;;;;;;OAMG;IACH,OAAO,CAAC,sBAAsB;IAgB9B;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAmBzB;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IA2BzB;;OAEG;IACH,OAAO,CAAC,kBAAkB;CAQ3B"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/providers/cursor/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,sBAAsB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAiBjG;;;;;;;;;;;;;GAaG;AACH,qBAAa,qBAAsB,YAAW,sBAAsB;IAClE;;;;;OAKG;IACG,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IA2B9D;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAEhC;;;;OAIG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAqBrC;;;;;;OAMG;IACG,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpE;;;;;;OAMG;IACH,OAAO,CAAC,sBAAsB;IAgB9B;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAmBzB;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IA2BzB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,oBAAoB;IA8B5B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,sBAAsB;CAyC/B"}
@@ -3,8 +3,10 @@
3
3
  *
4
4
  * Handles CLEO installation into OpenCode environments:
5
5
  * - Ensures AGENTS.md has CLEO @-references
6
+ * - Installs PreCompact hook shell shims + a JS plugin wrapper (T1013)
6
7
  *
7
8
  * @task T5240
9
+ * @task T1013
8
10
  */
9
11
  import type { AdapterInstallProvider, InstallOptions, InstallResult } from '@cleocode/contracts';
10
12
  /**
@@ -12,11 +14,17 @@ import type { AdapterInstallProvider, InstallOptions, InstallResult } from '@cle
12
14
  *
13
15
  * Manages CLEO's integration with OpenCode by:
14
16
  * 1. Ensuring AGENTS.md contains @-references to CLEO instruction files
17
+ * 2. Installing PreCompact hook shell templates + generating the JS plugin
18
+ * wrapper that spawns the shim on `experimental.session.compacting` (T1013).
15
19
  *
16
20
  * @remarks
17
21
  * Installation is idempotent -- running install multiple times on the same
18
- * project produces the same result. Only AGENTS.md is managed; OpenCode's
19
- * plugin system is handled separately by the hook provider.
22
+ * project produces the same result. OpenCode's plugin system is the native
23
+ * hook surface (OpenCode has no config-file hook registry like Claude Code or
24
+ * Cursor), so the installer writes a JS plugin that subscribes to the native
25
+ * event and spawns the shell shim as a child process. This keeps the DRY
26
+ * contract: all providers funnel through the shared `cleo-precompact-core.sh`
27
+ * helper and end up in the `cleo` CLI.
20
28
  */
21
29
  export declare class OpenCodeInstallProvider implements AdapterInstallProvider {
22
30
  /**
@@ -52,5 +60,42 @@ export declare class OpenCodeInstallProvider implements AdapterInstallProvider {
52
60
  * @returns true if the file was created or modified
53
61
  */
54
62
  private updateInstructionFile;
63
+ /**
64
+ * Install the CLEO PreCompact hook templates for OpenCode (T1013).
65
+ *
66
+ * OpenCode uses a JavaScript plugin system, not config-based hooks. The
67
+ * installer:
68
+ *
69
+ * 1. Writes the shared bash helper and OpenCode-flavoured `precompact.sh`
70
+ * to `<projectDir>/.opencode/plugins/hooks/` so the shim can be spawned
71
+ * as a child process.
72
+ * 2. Generates an OpenCode plugin `.opencode/plugins/cleo-precompact.js`
73
+ * that subscribes to `experimental.session.compacting` (CAAMP native
74
+ * event for the canonical `PreCompact`) and spawns the shim.
75
+ *
76
+ * Idempotent.
77
+ *
78
+ * @param projectDir - Project root directory.
79
+ * @returns Install summary, or `null` when no change was required.
80
+ *
81
+ * @task T1013
82
+ */
83
+ private installHookTemplates;
84
+ /**
85
+ * Write an OpenCode JavaScript plugin that spawns `precompact.sh` when the
86
+ * canonical `PreCompact` event fires. OpenCode exposes the event natively as
87
+ * `experimental.session.compacting` (see CAAMP `hook-mappings.json`).
88
+ *
89
+ * The generated file is idempotent — overwritten only when its content
90
+ * differs from the target on disk. Uses `child_process.spawn` so the bash
91
+ * shim runs in a separate process and does not block the compaction path.
92
+ *
93
+ * @param pluginsDir - Absolute path to `.opencode/plugins/`.
94
+ * @param shimPath - Absolute path to the installed `precompact.sh`.
95
+ * @returns `true` when the plugin file was written, `false` when unchanged.
96
+ *
97
+ * @task T1013
98
+ */
99
+ private writePrecompactPlugin;
55
100
  }
56
101
  //# sourceMappingURL=install.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/providers/opencode/install.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,sBAAsB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAajG;;;;;;;;;;GAUG;AACH,qBAAa,uBAAwB,YAAW,sBAAsB;IACpE;;;;;OAKG;IACG,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAoB9D;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAEhC;;;;OAIG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAgBrC;;;;;;OAMG;IACG,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpE;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;CA8B9B"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/providers/opencode/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,sBAAsB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAiBjG;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,uBAAwB,YAAW,sBAAsB;IACpE;;;;;OAKG;IACG,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IA4B9D;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAEhC;;;;OAIG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAgBrC;;;;;;OAMG;IACG,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpE;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IA+B7B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,oBAAoB;IAiC5B;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,qBAAqB;CA0C9B"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Shared hook-template installer for provider adapters.
3
+ *
4
+ * Each provider ships its own PreCompact shell shim under
5
+ * `packages/adapters/src/providers/<provider>/templates/hooks/` which sources
6
+ * the universal helper at
7
+ * `packages/adapters/src/providers/shared/templates/hooks/cleo-precompact-core.sh`.
8
+ *
9
+ * This module wires the templates into the provider's hooks directory at
10
+ * install time. Provider-specific {@link AdapterInstallProvider} implementations
11
+ * call {@link installProviderHookTemplates} with their own provider id, and the
12
+ * installer consults CAAMP's `hook-mappings.json` SSoT to verify the provider
13
+ * supports the required canonical event and handler type before writing.
14
+ *
15
+ * DRY invariant: all shims source the same core helper — adapter-specific
16
+ * shims only add provider-flavoured banners and `$CLEO_PRECOMPACT_*` env
17
+ * handling.
18
+ *
19
+ * @task T1013
20
+ * @epic T1000
21
+ */
22
+ /** Identifiers for providers that ship bash hook templates. */
23
+ export type HookTemplateProviderId = 'claude-code' | 'cursor' | 'opencode' | 'gemini-cli';
24
+ /**
25
+ * Result returned by {@link installProviderHookTemplates}.
26
+ *
27
+ * @remarks
28
+ * Paths are absolute and point at the filesystem locations the installer
29
+ * actually wrote to. When no template files needed copying (e.g. because the
30
+ * destination already contained identical files), `installedFiles` is empty
31
+ * and `skipped` carries the reason-keyed paths instead.
32
+ */
33
+ export interface InstallHookTemplatesResult {
34
+ /** Provider identifier the templates were installed for. */
35
+ provider: HookTemplateProviderId;
36
+ /** Absolute path to the hooks directory that received the templates. */
37
+ targetDir: string;
38
+ /** Absolute paths to files written during this install invocation. */
39
+ installedFiles: string[];
40
+ /** Files that were not written (already present and identical). */
41
+ skipped: string[];
42
+ }
43
+ /**
44
+ * Options for {@link installProviderHookTemplates}.
45
+ */
46
+ export interface InstallHookTemplatesOptions {
47
+ /** Provider to install hook templates for. */
48
+ provider: HookTemplateProviderId;
49
+ /** Absolute path to the hooks directory that should receive the shims. */
50
+ targetDir: string;
51
+ /**
52
+ * When `true`, overwrite existing files even if their contents match.
53
+ *
54
+ * @defaultValue `false`
55
+ */
56
+ force?: boolean;
57
+ }
58
+ /**
59
+ * Install the CLEO PreCompact hook templates for a provider.
60
+ *
61
+ * Writes two files into `targetDir`:
62
+ *
63
+ * 1. `cleo-precompact-core.sh` — universal helper (shared across all providers)
64
+ * 2. `<provider-shim>.sh` — provider-flavoured shim that sources the helper
65
+ *
66
+ * The shim invokes only the universal CLEO CLI (`cleo memory precompact-flush`
67
+ * and `cleo safestop …`) — adapters never reach into core internals.
68
+ *
69
+ * @param options - Installation target and provider id.
70
+ * @returns Paths written, paths skipped, and the resolved target directory.
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * import { homedir } from 'node:os';
75
+ * import { join } from 'node:path';
76
+ * import { installProviderHookTemplates } from '@cleocode/adapters';
77
+ *
78
+ * const result = installProviderHookTemplates({
79
+ * provider: 'claude-code',
80
+ * targetDir: join(homedir(), '.claude', 'hooks'),
81
+ * });
82
+ * // result.installedFiles includes both scripts on first run.
83
+ * ```
84
+ *
85
+ * @task T1013
86
+ * @public
87
+ */
88
+ export declare function installProviderHookTemplates(options: InstallHookTemplatesOptions): InstallHookTemplatesResult;
89
+ /**
90
+ * Resolve the source-side path of a provider's hook template for inspection
91
+ * and testing. Returns the absolute path where the installer will read from.
92
+ *
93
+ * @param provider - Provider identifier.
94
+ * @returns Absolute path to the provider's shim template file.
95
+ *
96
+ * @task T1013
97
+ * @public
98
+ */
99
+ export declare function getProviderHookTemplatePath(provider: HookTemplateProviderId): string;
100
+ /**
101
+ * Resolve the source-side path of the shared universal helper.
102
+ *
103
+ * @returns Absolute path to `cleo-precompact-core.sh` in the adapter package.
104
+ *
105
+ * @task T1013
106
+ * @public
107
+ */
108
+ export declare function getSharedHookCorePath(): string;
109
+ //# sourceMappingURL=hook-template-installer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-template-installer.d.ts","sourceRoot":"","sources":["../../../src/providers/shared/hook-template-installer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAUH,+DAA+D;AAC/D,MAAM,MAAM,sBAAsB,GAAG,aAAa,GAAG,QAAQ,GAAG,UAAU,GAAG,YAAY,CAAC;AAE1F;;;;;;;;GAQG;AACH,MAAM,WAAW,0BAA0B;IACzC,4DAA4D;IAC5D,QAAQ,EAAE,sBAAsB,CAAC;IACjC,wEAAwE;IACxE,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,mEAAmE;IACnE,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C,8CAA8C;IAC9C,QAAQ,EAAE,sBAAsB,CAAC;IACjC,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAqHD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,2BAA2B,GACnC,0BAA0B,CAyB5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,sBAAsB,GAAG,MAAM,CAEpF;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/adapters",
3
- "version": "2026.4.101",
3
+ "version": "2026.4.102",
4
4
  "description": "Unified provider adapters for CLEO (Claude Code, OpenCode, Cursor)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -15,8 +15,8 @@
15
15
  "@ai-sdk/anthropic": "^3.0.69",
16
16
  "@ai-sdk/openai": "^2.0.53",
17
17
  "ai": "^6.0.168",
18
- "@cleocode/caamp": "2026.4.101",
19
- "@cleocode/contracts": "2026.4.101"
18
+ "@cleocode/caamp": "2026.4.102",
19
+ "@cleocode/contracts": "2026.4.102"
20
20
  },
21
21
  "license": "MIT",
22
22
  "engines": {
@@ -29,7 +29,7 @@
29
29
  "devDependencies": {
30
30
  "@types/node": "^22.19.15",
31
31
  "vitest": "^4.1.4",
32
- "@cleocode/playbooks": "2026.4.101"
32
+ "@cleocode/playbooks": "2026.4.102"
33
33
  },
34
34
  "repository": {
35
35
  "type": "git",
package/src/index.ts CHANGED
@@ -84,5 +84,16 @@ export {
84
84
  OpenCodeInstallProvider,
85
85
  OpenCodeSpawnProvider,
86
86
  } from './providers/opencode/index.js';
87
+ export type {
88
+ HookTemplateProviderId,
89
+ InstallHookTemplatesOptions,
90
+ InstallHookTemplatesResult,
91
+ } from './providers/shared/hook-template-installer.js';
92
+ // T1013 — shared PreCompact hook template installer (DRY across providers).
93
+ export {
94
+ getProviderHookTemplatePath,
95
+ getSharedHookCorePath,
96
+ installProviderHookTemplates,
97
+ } from './providers/shared/hook-template-installer.js';
87
98
  export type { AdapterManifest } from './registry.js';
88
99
  export { discoverProviders, getProviderManifests } from './registry.js';
@@ -0,0 +1,137 @@
1
+ # CLEO Provider Adapters
2
+
3
+ This directory contains the per-provider adapters that integrate CLEO with
4
+ individual AI coding harnesses (Claude Code, Cursor, OpenCode, Gemini CLI,
5
+ Pi, etc.). Each adapter implements the contracts defined in
6
+ `@cleocode/contracts` and delegates universal behaviour to CLEO core through
7
+ the `cleo` CLI — adapters never reach into `@cleocode/core` internals at
8
+ runtime.
9
+
10
+ ## Directory layout
11
+
12
+ ```
13
+ providers/
14
+ ├── claude-code/
15
+ │ ├── adapter.ts # CLEOProviderAdapter implementation
16
+ │ ├── hooks.ts # AdapterHookProvider (event-name translation)
17
+ │ ├── install.ts # AdapterInstallProvider (filesystem wiring)
18
+ │ ├── spawn.ts # AdapterSpawnProvider
19
+ │ └── templates/
20
+ │ └── hooks/ # Provider-specific shell shims (see below)
21
+ ├── cursor/
22
+ │ ├── ...
23
+ │ └── templates/hooks/
24
+ ├── opencode/
25
+ │ ├── ...
26
+ │ └── templates/hooks/
27
+ ├── gemini-cli/
28
+ │ ├── ...
29
+ │ └── templates/hooks/
30
+ ├── pi/
31
+ │ ├── ...
32
+ │ └── templates/hooks/ # README only — Pi uses TS extensions, not shell
33
+ └── shared/
34
+ ├── hook-template-installer.ts # DRY installer for all providers
35
+ ├── paths.ts
36
+ ├── transcript-reader.ts
37
+ └── templates/hooks/
38
+ └── cleo-precompact-core.sh # Shared universal helper (single SSoT)
39
+ ```
40
+
41
+ ## Hook Template Architecture (T1013)
42
+
43
+ The provider adapters own all harness-specific hook templates. The core
44
+ package (`@cleocode/core`) ships only the universal business logic
45
+ (`src/memory/precompact-flush.ts`, `src/hooks/handlers/*`) and the `cleo`
46
+ CLI — never bash.
47
+
48
+ ### Layered design
49
+
50
+ 1. **Universal layer** — `shared/templates/hooks/cleo-precompact-core.sh`
51
+ contains the one-and-only implementation of the pre-compact flush +
52
+ safestop sequence. It invokes the CLEO CLI (`cleo memory precompact-flush`
53
+ and `cleo safestop …`). No provider knows any CLEO internals; every
54
+ provider ends up at the same CLI surface.
55
+
56
+ 2. **Provider shim layer** — each harness-specific directory ships a tiny
57
+ wrapper that sources the universal helper and adds only provider-flavoured
58
+ banners / env handling. Per-provider filenames match the harness's native
59
+ event vocabulary:
60
+
61
+ | Provider | Shim | Canonical event | Native event |
62
+ |--------------|-----------------------------------------|-----------------|---------------------------|
63
+ | claude-code | `precompact-safestop.sh` | `PreCompact` | `PreCompact` |
64
+ | cursor | `precompact.sh` | `PreCompact` | `preCompact` |
65
+ | opencode | `precompact.sh` + `cleo-precompact.js` | `PreCompact` | `experimental.session.compacting` |
66
+ | gemini-cli | `precompact.sh` | `PreCompact` | `PreCompress` |
67
+ | pi | *README-only* (uses TS extension) | `PreCompact` | `context` |
68
+
69
+ The event mappings are sourced from `packages/caamp/providers/hook-mappings.json`
70
+ (the CAAMP SSoT). Provider adapters import the translation via
71
+ `@cleocode/caamp`'s `toNative()` / `getProviderHookProfile()` APIs rather
72
+ than hardcoding names.
73
+
74
+ 3. **Installer layer** — `shared/hook-template-installer.ts` exposes a single
75
+ `installProviderHookTemplates({ provider, targetDir })` function that:
76
+ 1. Resolves the provider's shim + the shared helper from this package.
77
+ 2. Copies both into the provided target directory (idempotent).
78
+ 3. Returns an install summary for reporting.
79
+
80
+ Each provider's `AdapterInstallProvider.install(...)` method calls this
81
+ installer and then wires the shim into the harness's configuration
82
+ surface:
83
+
84
+ - Claude Code: append a `PreCompact` entry to `~/.claude/settings.json`.
85
+ - Cursor: append a `preCompact` entry to `.cursor/hooks.json`.
86
+ - OpenCode: generate a JS plugin at
87
+ `.opencode/plugins/cleo-precompact.js` that `spawn()`s the shim.
88
+ - Gemini CLI: (planned) append a `PreCompress` entry to
89
+ `~/.gemini/settings.json`.
90
+ - Pi: (planned) write a TS extension that calls the core handler.
91
+
92
+ ### Why this split?
93
+
94
+ Before T1013 the pre-compact hook lived in
95
+ `packages/core/templates/hooks/precompact-safestop.sh` — Claude-Code-specific
96
+ bash buried inside the provider-neutral core package. Moving the file into
97
+ `packages/adapters/src/providers/claude-code/templates/hooks/` and adding
98
+ equivalent templates under the other provider directories restores the
99
+ architectural boundary:
100
+
101
+ - **Core** owns universal CLEO logic (`precompact-flush.ts`,
102
+ `memory-sqlite.ts`, session handling).
103
+ - **Adapters** own harness-specific wiring (shell shims, config fragments,
104
+ plugin generators).
105
+ - **CAAMP** owns the event-name translation SSoT (`hook-mappings.json`).
106
+
107
+ Provider adapters are the only place that knows whether a harness uses
108
+ `.claude/settings.json` versus `.cursor/hooks.json` versus a JS plugin —
109
+ and they all funnel execution back through the same `cleo` CLI.
110
+
111
+ ### Adding a new harness
112
+
113
+ 1. Add the provider's mapping to `packages/caamp/providers/hook-mappings.json`
114
+ (the SSoT). At minimum populate `hookSystem`, `hookConfigPath`,
115
+ `handlerTypes`, and the `PreCompact` entry under `mappings`.
116
+ 2. Create `<provider>/templates/hooks/<shim>.sh` that sources the shared
117
+ helper. Keep the shim small — only provider-flavoured echo / exit handling
118
+ goes here.
119
+ 3. Teach `shared/hook-template-installer.ts` about the new provider id by
120
+ adding it to `HookTemplateProviderId` and `PROVIDER_SHIM`.
121
+ 4. Add the provider's `AdapterInstallProvider` method that calls
122
+ `installProviderHookTemplates` + writes the harness-specific config
123
+ fragment.
124
+ 5. Ship tests under `<provider>/__tests__/hooks-install.test.ts`.
125
+
126
+ ### Constraints
127
+
128
+ - **Shims MUST invoke CLEO only through the CLI.** No `require('@cleocode/core')`,
129
+ no reaching into core internals from bash.
130
+ - **Shims MUST be idempotent on install and uninstall.** Re-running the
131
+ installer must not duplicate config entries.
132
+ - **The shared helper is the one-and-only contract.** Duplicating its logic
133
+ into provider shims is a DRY violation — add provider-specific behaviour
134
+ only to the shim wrapper.
135
+ - **Hook failures MUST NOT block the harness.** All CLEO invocations in the
136
+ shim tolerate non-zero exits via `|| true` so the host compaction path is
137
+ never interrupted.
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Tests for the Claude Code PreCompact hook-template installer (T1013).
3
+ *
4
+ * Validates that:
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.
9
+ * - Repeat invocations are idempotent (no duplicate settings entries).
10
+ * - The source templates contain the universal CLEO CLI invocations so the
11
+ * bash contract remains DRY across providers.
12
+ *
13
+ * The tests use real filesystem writes under a scoped tmp directory that
14
+ * impersonates `$HOME` via the `HOME` env var, so no user config is touched.
15
+ *
16
+ * @task T1013
17
+ * @epic T1000
18
+ */
19
+
20
+ import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
21
+ import { tmpdir } from 'node:os';
22
+ import { join } from 'node:path';
23
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
24
+ import { ClaudeCodeInstallProvider } from '../install.js';
25
+
26
+ describe('ClaudeCodeInstallProvider — PreCompact hook templates', () => {
27
+ let fakeHome: string;
28
+ let realHome: string | undefined;
29
+ let projectDir: string;
30
+
31
+ beforeEach(() => {
32
+ realHome = process.env.HOME;
33
+ fakeHome = mkdtempSync(join(tmpdir(), 'cleo-claude-install-'));
34
+ projectDir = mkdtempSync(join(tmpdir(), 'cleo-claude-project-'));
35
+ process.env.HOME = fakeHome;
36
+ // Suppress unused-var lint on Windows (USERPROFILE pathway not exercised here).
37
+ void fakeHome;
38
+ });
39
+
40
+ afterEach(() => {
41
+ if (realHome !== undefined) {
42
+ process.env.HOME = realHome;
43
+ } else {
44
+ delete process.env.HOME;
45
+ }
46
+ rmSync(fakeHome, { recursive: true, force: true });
47
+ rmSync(projectDir, { recursive: true, force: true });
48
+ });
49
+
50
+ it('installs both bash templates into $HOME/.claude/hooks/', async () => {
51
+ const provider = new ClaudeCodeInstallProvider();
52
+
53
+ const result = await provider.install({ projectDir });
54
+ expect(result.success).toBe(true);
55
+
56
+ const hookTemplates = (result.details?.hookTemplates ?? null) as {
57
+ templates: { installedFiles: string[]; targetDir: string };
58
+ settingsEntryAdded: boolean;
59
+ } | null;
60
+
61
+ expect(hookTemplates).not.toBeNull();
62
+ expect(hookTemplates?.templates.targetDir).toBe(join(fakeHome, '.claude', 'hooks'));
63
+ const installed = hookTemplates?.templates.installedFiles ?? [];
64
+ expect(installed.some((p) => p.endsWith('cleo-precompact-core.sh'))).toBe(true);
65
+ expect(installed.some((p) => p.endsWith('precompact-safestop.sh'))).toBe(true);
66
+
67
+ // Installed shim sources the shared helper.
68
+ const shim = readFileSync(
69
+ join(fakeHome, '.claude', 'hooks', 'precompact-safestop.sh'),
70
+ 'utf-8',
71
+ );
72
+ expect(shim).toContain('cleo-precompact-core.sh');
73
+ // Shared helper invokes the universal CLEO CLI.
74
+ const core = readFileSync(
75
+ join(fakeHome, '.claude', 'hooks', 'cleo-precompact-core.sh'),
76
+ 'utf-8',
77
+ );
78
+ expect(core).toContain('cleo memory precompact-flush');
79
+ expect(core).toContain('cleo');
80
+ expect(core).toMatch(/cleo_cmd.*safestop/);
81
+ });
82
+
83
+ it('writes a PreCompact entry into $HOME/.claude/settings.json tagged # cleo-hook', async () => {
84
+ const provider = new ClaudeCodeInstallProvider();
85
+ await provider.install({ projectDir });
86
+
87
+ const settingsPath = join(fakeHome, '.claude', 'settings.json');
88
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf-8')) as {
89
+ hooks?: Record<string, Array<{ hooks?: Array<{ command?: string; type?: string }> }>>;
90
+ };
91
+
92
+ const preCompact = settings.hooks?.PreCompact ?? [];
93
+ expect(preCompact.length).toBeGreaterThan(0);
94
+ const firstEntry = preCompact[0];
95
+ expect(firstEntry).toBeDefined();
96
+ const firstHook = firstEntry?.hooks?.[0];
97
+ expect(firstHook?.type).toBe('command');
98
+ expect(firstHook?.command).toContain('precompact-safestop.sh');
99
+ expect(firstHook?.command).toContain('# cleo-hook');
100
+ });
101
+
102
+ it('is idempotent — re-running install does not duplicate the PreCompact entry', async () => {
103
+ const provider = new ClaudeCodeInstallProvider();
104
+ await provider.install({ projectDir });
105
+ await provider.install({ projectDir });
106
+
107
+ const settingsPath = join(fakeHome, '.claude', 'settings.json');
108
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf-8')) as {
109
+ hooks?: { PreCompact?: unknown[] };
110
+ };
111
+ expect((settings.hooks?.PreCompact ?? []).length).toBe(1);
112
+ });
113
+ });