@cleocode/adapters 2026.10.4 → 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 (37) hide show
  1. package/dist/index.js +1608 -1380
  2. package/dist/index.js.map +3 -3
  3. package/dist/providers/claude-code/adapter.d.ts.map +1 -1
  4. package/dist/providers/claude-code/hooks.d.ts +43 -17
  5. package/dist/providers/claude-code/hooks.d.ts.map +1 -1
  6. package/dist/providers/claude-code/install.d.ts +42 -14
  7. package/dist/providers/claude-code/install.d.ts.map +1 -1
  8. package/dist/providers/claude-code/paths.d.ts +77 -0
  9. package/dist/providers/claude-code/paths.d.ts.map +1 -1
  10. package/dist/providers/codex/install.d.ts.map +1 -1
  11. package/dist/providers/cursor/install.d.ts.map +1 -1
  12. package/dist/providers/gemini-cli/install.d.ts.map +1 -1
  13. package/dist/providers/kimi/install.d.ts.map +1 -1
  14. package/dist/providers/openai-sdk/install.d.ts.map +1 -1
  15. package/dist/providers/opencode/install.d.ts.map +1 -1
  16. package/dist/providers/pi/install.d.ts.map +1 -1
  17. package/dist/providers/shared/heavy-command-hook-install.d.ts.map +1 -1
  18. package/dist/providers/shared/project-instruction-file.d.ts +30 -0
  19. package/dist/providers/shared/project-instruction-file.d.ts.map +1 -0
  20. package/package.json +5 -5
  21. package/src/__tests__/claude-code-adapter.test.ts +11 -6
  22. package/src/providers/claude-code/__tests__/hooks-install.test.ts +20 -17
  23. package/src/providers/claude-code/__tests__/no-user-global-write.test.ts +293 -0
  24. package/src/providers/claude-code/adapter.ts +3 -1
  25. package/src/providers/claude-code/hooks.ts +138 -51
  26. package/src/providers/claude-code/install.ts +123 -52
  27. package/src/providers/claude-code/paths.ts +159 -1
  28. package/src/providers/codex/install.ts +4 -2
  29. package/src/providers/cursor/install.ts +26 -12
  30. package/src/providers/gemini-cli/install.ts +4 -2
  31. package/src/providers/kimi/install.ts +4 -2
  32. package/src/providers/openai-sdk/install.ts +5 -2
  33. package/src/providers/opencode/install.ts +4 -2
  34. package/src/providers/pi/install.ts +4 -2
  35. package/src/providers/shared/__tests__/heavy-command-hook-install.test.ts +20 -0
  36. package/src/providers/shared/heavy-command-hook-install.ts +6 -1
  37. package/src/providers/shared/project-instruction-file.ts +47 -0
@@ -14,6 +14,7 @@ import { join } from 'node:path';
14
14
  import { ensureProviderInstructionFile } from '@cleocode/caamp';
15
15
  import type { AdapterInstallProvider, InstallOptions, InstallResult } from '@cleocode/contracts';
16
16
  import { getCleoTemplatesTildePath } from '../shared/paths.js';
17
+ import { ensureProjectInstructionFile } from '../shared/project-instruction-file.js';
17
18
 
18
19
  /**
19
20
  * Install provider for Kimi.
@@ -58,15 +59,16 @@ export class KimiInstallProvider implements AdapterInstallProvider {
58
59
  const installedAt = new Date().toISOString();
59
60
  const details: Record<string, unknown> = {};
60
61
 
61
- const result = await ensureProviderInstructionFile('kimi', projectDir, {
62
+ const result = await ensureProjectInstructionFile('kimi', projectDir, {
62
63
  references: [`@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`, '@.cleo/memory-bridge.md'],
63
64
  scope: 'project',
64
65
  });
65
66
 
66
- const instructionFileUpdated = result.action !== 'intact';
67
+ const instructionFileUpdated = result !== null && result.action !== 'intact';
67
68
  if (instructionFileUpdated) {
68
69
  details.instructionFile = result.filePath;
69
70
  }
71
+ if (result === null) details.instructionFile = 'skipped'; // T13227: $HOME project
70
72
 
71
73
  return {
72
74
  success: true,
@@ -14,6 +14,7 @@ import { join } from 'node:path';
14
14
  import { ensureProviderInstructionFile } from '@cleocode/caamp';
15
15
  import type { AdapterInstallProvider, InstallOptions, InstallResult } from '@cleocode/contracts';
16
16
  import { getCleoTemplatesTildePath } from '../shared/paths.js';
17
+ import { ensureProjectInstructionFile } from '../shared/project-instruction-file.js';
17
18
 
18
19
  /**
19
20
  * Install provider for the OpenAI SDK adapter (Vercel AI SDK).
@@ -40,15 +41,17 @@ export class OpenAiSdkInstallProvider implements AdapterInstallProvider {
40
41
  const details: Record<string, unknown> = {};
41
42
 
42
43
  // Step 1: Ensure AGENTS.md has @-references via CAAMP registry
43
- const instructionResult = await ensureProviderInstructionFile('openai-sdk', projectDir, {
44
+ const instructionResult = await ensureProjectInstructionFile('openai-sdk', projectDir, {
44
45
  references: [`@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`, '@.cleo/memory-bridge.md'],
45
46
  scope: 'project',
46
47
  });
47
48
 
48
- const instructionFileUpdated = instructionResult.action !== 'intact';
49
+ const instructionFileUpdated =
50
+ instructionResult !== null && instructionResult.action !== 'intact';
49
51
  if (instructionFileUpdated) {
50
52
  details.instructionFile = instructionResult.filePath;
51
53
  }
54
+ if (instructionResult === null) details.instructionFile = 'skipped'; // T13227: $HOME project
52
55
 
53
56
  // Step 2: Create .openai config directory stub
54
57
  const configCreated = this.ensureConfigDir(projectDir);
@@ -25,6 +25,7 @@ import {
25
25
  installProviderHookTemplates,
26
26
  } from '../shared/hook-template-installer.js';
27
27
  import { getCleoTemplatesTildePath } from '../shared/paths.js';
28
+ import { ensureProjectInstructionFile } from '../shared/project-instruction-file.js';
28
29
 
29
30
  /**
30
31
  * Install provider for OpenCode.
@@ -57,15 +58,16 @@ export class OpenCodeInstallProvider implements AdapterInstallProvider {
57
58
  const details: Record<string, unknown> = {};
58
59
 
59
60
  // Step 1: Ensure AGENTS.md has @-references via CAAMP canonical API.
60
- const instructResult = await ensureProviderInstructionFile('opencode', projectDir, {
61
+ const instructResult = await ensureProjectInstructionFile('opencode', projectDir, {
61
62
  scope: 'project',
62
63
  references: [`@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`, '@.cleo/memory-bridge.md'],
63
64
  });
64
65
 
65
- const instructionFileUpdated = instructResult.action !== 'intact';
66
+ const instructionFileUpdated = instructResult !== null && instructResult.action !== 'intact';
66
67
  if (instructionFileUpdated) {
67
68
  details.instructionFile = join(projectDir, instructResult.instructFile);
68
69
  }
70
+ if (instructResult === null) details.instructionFile = 'skipped'; // T13227: $HOME project
69
71
 
70
72
  // Step 2 (T1013): Install PreCompact hook templates + generate the JS
71
73
  // plugin wrapper that spawns the bash shim on
@@ -19,6 +19,7 @@ import { join } from 'node:path';
19
19
  import { ensureProviderInstructionFile } from '@cleocode/caamp';
20
20
  import type { AdapterInstallProvider, InstallOptions, InstallResult } from '@cleocode/contracts';
21
21
  import { getCleoTemplatesTildePath } from '../shared/paths.js';
22
+ import { ensureProjectInstructionFile } from '../shared/project-instruction-file.js';
22
23
 
23
24
  /**
24
25
  * CLEO @-references injected into Pi instruction files.
@@ -55,15 +56,16 @@ export class PiInstallProvider implements AdapterInstallProvider {
55
56
  const details: Record<string, unknown> = {};
56
57
 
57
58
  // Step 1: Ensure project AGENTS.md has @-references via CAAMP canonical API.
58
- const projectResult = await ensureProviderInstructionFile('pi', projectDir, {
59
+ const projectResult = await ensureProjectInstructionFile('pi', projectDir, {
59
60
  scope: 'project',
60
61
  references: getCleoReferences(),
61
62
  });
62
63
 
63
- const projectUpdated = projectResult.action !== 'intact';
64
+ const projectUpdated = projectResult !== null && projectResult.action !== 'intact';
64
65
  if (projectUpdated) {
65
66
  details.instructionFile = join(projectDir, projectResult.instructFile);
66
67
  }
68
+ if (projectResult === null) details.instructionFile = 'skipped'; // T13227: $HOME project
67
69
 
68
70
  // Step 2: Ensure global AGENTS.md has @-references (best-effort).
69
71
  // CAAMP resolves pathGlobal from the registry ($HOME/.pi/agent).
@@ -43,6 +43,7 @@ import { pathToFileURL } from 'node:url';
43
43
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
44
44
  import { ClaudeCodeInstallProvider } from '../../claude-code/install.js';
45
45
  import { CodexInstallProvider } from '../../codex/install.js';
46
+ import { CursorInstallProvider } from '../../cursor/install.js';
46
47
  import { OpenCodeInstallProvider } from '../../opencode/install.js';
47
48
  import {
48
49
  clearOlderCleoMarkers,
@@ -591,6 +592,25 @@ describe('installer wiring', () => {
591
592
  false,
592
593
  );
593
594
  expect(isUserHomeDir(join(home, 'project'))).toBe(false);
595
+ // T13227: no instruction file at $HOME either; providers load ~/CLAUDE.md
596
+ // and ~/AGENTS.md into every session under it.
597
+ expect(results.map((r) => r.details?.instructionFile)).toEqual([
598
+ 'skipped',
599
+ 'skipped',
600
+ 'skipped',
601
+ ]);
602
+ expect(existsSync(join(home, 'CLAUDE.md'))).toBe(false);
603
+ expect(existsSync(join(home, 'AGENTS.md'))).toBe(false);
604
+ });
605
+
606
+ it("T13257: Cursor at $HOME skips its hooks (~/.cursor/hooks.json is Cursor's user-global hooks) and rule files", async () => {
607
+ const home = process.env.HOME as string;
608
+ const r = await new CursorInstallProvider().install({ projectDir: home });
609
+ expect(r.details?.hookTemplates).toBe('skipped');
610
+ expect(r.details?.instructionFiles).toBe('skipped');
611
+ expect(existsSync(join(home, '.cursor', 'hooks.json'))).toBe(false);
612
+ expect(existsSync(join(home, '.cursor', 'hooks'))).toBe(false);
613
+ expect(existsSync(join(home, '.cursorrules'))).toBe(false);
594
614
  });
595
615
 
596
616
  it('Codex and opencode install only when a mode is passed', async () => {
@@ -101,9 +101,14 @@ export const HEAVY_TOOL_WORDS: readonly string[] = Object.freeze([
101
101
  */
102
102
  export type HeavyHookSyncResult = 'installed' | 'updated' | 'removed' | 'unchanged' | 'skipped';
103
103
 
104
+ /**
105
+ * `path` through the native realpath, which returns the case stored on disk,
106
+ * so a differently cased spelling of the home directory on a case-insensitive
107
+ * volume still matches it (T13227 review).
108
+ */
104
109
  function canonical(path: string): string {
105
110
  try {
106
- return realpathSync(path);
111
+ return realpathSync.native(path);
107
112
  } catch {
108
113
  return resolve(path);
109
114
  }
@@ -0,0 +1,47 @@
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
+
14
+ import {
15
+ type EnsureProviderInstructionFileOptions,
16
+ type EnsureProviderInstructionFileResult,
17
+ ensureProviderInstructionFile,
18
+ HomeInstructionFileError,
19
+ } from '@cleocode/caamp';
20
+
21
+ /**
22
+ * Ensure a provider's project instruction file, or `null` when the project is
23
+ * the home directory and the file was refused (nothing written).
24
+ *
25
+ * @param providerId - CAAMP provider id.
26
+ * @param projectDir - the project root.
27
+ * @param options - references, content, scope.
28
+ * @returns the CAAMP result, or `null` when skipped at `$HOME`.
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const r = await ensureProjectInstructionFile('codex', projectDir, { scope: 'project' });
33
+ * if (r === null) details.instructionFile = 'skipped';
34
+ * ```
35
+ */
36
+ export async function ensureProjectInstructionFile(
37
+ providerId: string,
38
+ projectDir: string,
39
+ options: EnsureProviderInstructionFileOptions,
40
+ ): Promise<EnsureProviderInstructionFileResult | null> {
41
+ try {
42
+ return await ensureProviderInstructionFile(providerId, projectDir, options);
43
+ } catch (err) {
44
+ if (err instanceof HomeInstructionFileError) return null;
45
+ throw err;
46
+ }
47
+ }