@cleocode/adapters 2026.5.37 → 2026.5.40

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.
@@ -2,28 +2,29 @@
2
2
  * Gemini CLI Install Provider
3
3
  *
4
4
  * Handles CLEO installation into Gemini CLI environments:
5
- * - Ensures AGENTS.md has CLEO @-references
5
+ * - Ensures GEMINI.md has CLEO @-references (via CAAMP registry)
6
6
  *
7
7
  * @task T161
8
8
  * @epic T134
9
+ * @task T9018
9
10
  */
10
11
 
11
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
12
+ import { existsSync, readFileSync } from 'node:fs';
12
13
  import { join } from 'node:path';
14
+ import { ensureProviderInstructionFile } from '@cleocode/caamp';
13
15
  import type { AdapterInstallProvider, InstallOptions, InstallResult } from '@cleocode/contracts';
14
-
15
- /** Lines that should appear in AGENTS.md to reference CLEO. */
16
- const INSTRUCTION_REFERENCES = ['@~/.cleo/templates/CLEO-INJECTION.md', '@.cleo/memory-bridge.md'];
16
+ import { getCleoTemplatesTildePath } from '../shared/paths.js';
17
17
 
18
18
  /**
19
19
  * Install provider for Gemini CLI.
20
20
  *
21
21
  * Manages CLEO's integration with Gemini CLI by:
22
- * 1. Ensuring AGENTS.md contains @-references to CLEO instruction files
22
+ * 1. Ensuring GEMINI.md contains @-references to CLEO instruction files
23
+ * (via CAAMP registry — single source of truth for instruction file name)
23
24
  *
24
25
  * @remarks
25
26
  * Installation is idempotent -- running install multiple times on the same
26
- * project produces the same result. Only AGENTS.md is managed; Gemini CLI
27
+ * project produces the same result. Only GEMINI.md is managed; Gemini CLI
27
28
  * does not have an MCP or plugin registration mechanism.
28
29
  *
29
30
  * @task T161
@@ -40,13 +41,16 @@ export class GeminiCliInstallProvider implements AdapterInstallProvider {
40
41
  async install(options: InstallOptions): Promise<InstallResult> {
41
42
  const { projectDir } = options;
42
43
  const installedAt = new Date().toISOString();
43
- let instructionFileUpdated = false;
44
44
  const details: Record<string, unknown> = {};
45
45
 
46
- // Step 1: Ensure AGENTS.md has @-references
47
- instructionFileUpdated = this.updateInstructionFile(projectDir);
46
+ const result = await ensureProviderInstructionFile('gemini-cli', projectDir, {
47
+ references: [`@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`, '@.cleo/memory-bridge.md'],
48
+ scope: 'project',
49
+ });
50
+
51
+ const instructionFileUpdated = result.action !== 'intact';
48
52
  if (instructionFileUpdated) {
49
- details.instructionFile = join(projectDir, 'AGENTS.md');
53
+ details.instructionFile = result.filePath;
50
54
  }
51
55
 
52
56
  return {
@@ -60,7 +64,7 @@ export class GeminiCliInstallProvider implements AdapterInstallProvider {
60
64
  /**
61
65
  * Uninstall CLEO from the Gemini CLI environment.
62
66
  *
63
- * Does not remove AGENTS.md references (they are harmless if CLEO is not present).
67
+ * Does not remove GEMINI.md references (they are harmless if CLEO is not present).
64
68
  * @task T161
65
69
  */
66
70
  async uninstall(): Promise<void> {
@@ -70,15 +74,16 @@ export class GeminiCliInstallProvider implements AdapterInstallProvider {
70
74
  /**
71
75
  * Check whether CLEO is installed in the Gemini CLI environment.
72
76
  *
73
- * Checks for CLEO references in AGENTS.md.
77
+ * Checks for CLEO references in GEMINI.md.
74
78
  * @task T161
75
79
  */
76
80
  async isInstalled(): Promise<boolean> {
77
- const agentsMdPath = join(process.cwd(), 'AGENTS.md');
78
- if (existsSync(agentsMdPath)) {
81
+ const instructionRef = `@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`;
82
+ const geminiMdPath = join(process.cwd(), 'GEMINI.md');
83
+ if (existsSync(geminiMdPath)) {
79
84
  try {
80
- const content = readFileSync(agentsMdPath, 'utf-8');
81
- if (INSTRUCTION_REFERENCES.some((ref) => content.includes(ref))) {
85
+ const content = readFileSync(geminiMdPath, 'utf-8');
86
+ if (content.includes(instructionRef)) {
82
87
  return true;
83
88
  }
84
89
  } catch {
@@ -90,49 +95,17 @@ export class GeminiCliInstallProvider implements AdapterInstallProvider {
90
95
  }
91
96
 
92
97
  /**
93
- * Ensure AGENTS.md contains @-references to CLEO instruction files.
98
+ * Ensure GEMINI.md contains @-references to CLEO instruction files.
94
99
  *
95
- * Creates AGENTS.md if it does not exist. Appends any missing references.
100
+ * Creates GEMINI.md if it does not exist. Appends any missing references.
96
101
  *
97
102
  * @param projectDir - Project root directory
98
103
  * @task T161
99
104
  */
100
105
  async ensureInstructionReferences(projectDir: string): Promise<void> {
101
- this.updateInstructionFile(projectDir);
102
- }
103
-
104
- /**
105
- * Update AGENTS.md with CLEO @-references.
106
- *
107
- * @param projectDir - Project root directory
108
- * @returns true if the file was created or modified
109
- */
110
- private updateInstructionFile(projectDir: string): boolean {
111
- const agentsMdPath = join(projectDir, 'AGENTS.md');
112
- let content = '';
113
- let existed = false;
114
-
115
- if (existsSync(agentsMdPath)) {
116
- content = readFileSync(agentsMdPath, 'utf-8');
117
- existed = true;
118
- }
119
-
120
- const missingRefs = INSTRUCTION_REFERENCES.filter((ref) => !content.includes(ref));
121
-
122
- if (missingRefs.length === 0) {
123
- return false;
124
- }
125
-
126
- const refsBlock = missingRefs.join('\n');
127
-
128
- if (existed) {
129
- const separator = content.endsWith('\n') ? '' : '\n';
130
- content = content + separator + refsBlock + '\n';
131
- } else {
132
- content = refsBlock + '\n';
133
- }
134
-
135
- writeFileSync(agentsMdPath, content, 'utf-8');
136
- return true;
106
+ await ensureProviderInstructionFile('gemini-cli', projectDir, {
107
+ references: [`@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`, '@.cleo/memory-bridge.md'],
108
+ scope: 'project',
109
+ });
137
110
  }
138
111
  }
@@ -2,24 +2,25 @@
2
2
  * Kimi Install Provider
3
3
  *
4
4
  * Handles CLEO installation into Kimi environments:
5
- * - Ensures AGENTS.md has CLEO @-references
5
+ * - Ensures AGENTS.md has CLEO @-references (via CAAMP registry)
6
6
  *
7
7
  * @task T163
8
8
  * @epic T134
9
+ * @task T9018
9
10
  */
10
11
 
11
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
12
+ import { existsSync, readFileSync } from 'node:fs';
12
13
  import { join } from 'node:path';
14
+ import { ensureProviderInstructionFile } from '@cleocode/caamp';
13
15
  import type { AdapterInstallProvider, InstallOptions, InstallResult } from '@cleocode/contracts';
14
-
15
- /** Lines that should appear in AGENTS.md to reference CLEO. */
16
- const INSTRUCTION_REFERENCES = ['@~/.cleo/templates/CLEO-INJECTION.md', '@.cleo/memory-bridge.md'];
16
+ import { getCleoTemplatesTildePath } from '../shared/paths.js';
17
17
 
18
18
  /**
19
19
  * Install provider for Kimi.
20
20
  *
21
21
  * Manages CLEO's integration with Kimi by:
22
22
  * 1. Ensuring AGENTS.md contains @-references to CLEO instruction files
23
+ * (via CAAMP registry — single source of truth for instruction file name)
23
24
  *
24
25
  * @remarks
25
26
  * Installation is idempotent -- running install multiple times on the same
@@ -40,13 +41,16 @@ export class KimiInstallProvider implements AdapterInstallProvider {
40
41
  async install(options: InstallOptions): Promise<InstallResult> {
41
42
  const { projectDir } = options;
42
43
  const installedAt = new Date().toISOString();
43
- let instructionFileUpdated = false;
44
44
  const details: Record<string, unknown> = {};
45
45
 
46
- // Step 1: Ensure AGENTS.md has @-references
47
- instructionFileUpdated = this.updateInstructionFile(projectDir);
46
+ const result = await ensureProviderInstructionFile('kimi', projectDir, {
47
+ references: [`@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`, '@.cleo/memory-bridge.md'],
48
+ scope: 'project',
49
+ });
50
+
51
+ const instructionFileUpdated = result.action !== 'intact';
48
52
  if (instructionFileUpdated) {
49
- details.instructionFile = join(projectDir, 'AGENTS.md');
53
+ details.instructionFile = result.filePath;
50
54
  }
51
55
 
52
56
  return {
@@ -74,11 +78,12 @@ export class KimiInstallProvider implements AdapterInstallProvider {
74
78
  * @task T163
75
79
  */
76
80
  async isInstalled(): Promise<boolean> {
81
+ const instructionRef = `@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`;
77
82
  const agentsMdPath = join(process.cwd(), 'AGENTS.md');
78
83
  if (existsSync(agentsMdPath)) {
79
84
  try {
80
85
  const content = readFileSync(agentsMdPath, 'utf-8');
81
- if (INSTRUCTION_REFERENCES.some((ref) => content.includes(ref))) {
86
+ if (content.includes(instructionRef)) {
82
87
  return true;
83
88
  }
84
89
  } catch {
@@ -98,41 +103,9 @@ export class KimiInstallProvider implements AdapterInstallProvider {
98
103
  * @task T163
99
104
  */
100
105
  async ensureInstructionReferences(projectDir: string): Promise<void> {
101
- this.updateInstructionFile(projectDir);
102
- }
103
-
104
- /**
105
- * Update AGENTS.md with CLEO @-references.
106
- *
107
- * @param projectDir - Project root directory
108
- * @returns true if the file was created or modified
109
- */
110
- private updateInstructionFile(projectDir: string): boolean {
111
- const agentsMdPath = join(projectDir, 'AGENTS.md');
112
- let content = '';
113
- let existed = false;
114
-
115
- if (existsSync(agentsMdPath)) {
116
- content = readFileSync(agentsMdPath, 'utf-8');
117
- existed = true;
118
- }
119
-
120
- const missingRefs = INSTRUCTION_REFERENCES.filter((ref) => !content.includes(ref));
121
-
122
- if (missingRefs.length === 0) {
123
- return false;
124
- }
125
-
126
- const refsBlock = missingRefs.join('\n');
127
-
128
- if (existed) {
129
- const separator = content.endsWith('\n') ? '' : '\n';
130
- content = content + separator + refsBlock + '\n';
131
- } else {
132
- content = refsBlock + '\n';
133
- }
134
-
135
- writeFileSync(agentsMdPath, content, 'utf-8');
136
- return true;
106
+ await ensureProviderInstructionFile('kimi', projectDir, {
107
+ references: [`@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`, '@.cleo/memory-bridge.md'],
108
+ scope: 'project',
109
+ });
137
110
  }
138
111
  }
@@ -23,11 +23,14 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
23
23
  // Hoist shared state so vi.mock factories can reference it
24
24
  // ---------------------------------------------------------------------------
25
25
 
26
- const { generateTextCalls, mockRunState, mockFsState } = vi.hoisted(() => {
26
+ const { generateTextCalls, mockRunState, mockFsState, mockCaampState } = vi.hoisted(() => {
27
27
  return {
28
28
  generateTextCalls: [] as Array<{ model: unknown; system?: string; prompt: string }>,
29
29
  mockRunState: { output: 'mock output', shouldThrow: false },
30
30
  mockFsState: { exists: false, content: '' },
31
+ mockCaampState: {
32
+ action: 'created' as 'created' | 'added' | 'consolidated' | 'updated' | 'intact',
33
+ },
31
34
  };
32
35
  });
33
36
 
@@ -52,7 +55,7 @@ vi.mock('ai', () => ({
52
55
  }));
53
56
 
54
57
  // ---------------------------------------------------------------------------
55
- // Mock node:fs for install provider tests
58
+ // Mock node:fs for install provider tests (ensureConfigDir + isInstalled)
56
59
  // ---------------------------------------------------------------------------
57
60
 
58
61
  vi.mock('node:fs', async (importOriginal) => {
@@ -61,6 +64,7 @@ vi.mock('node:fs', async (importOriginal) => {
61
64
  ...actual,
62
65
  existsSync: vi.fn((path: string) => {
63
66
  if (typeof path === 'string' && path.includes('AGENTS.md')) return mockFsState.exists;
67
+ // .openai config dir: report not existing so we can test creation
64
68
  return false;
65
69
  }),
66
70
  readFileSync: vi.fn(() => mockFsState.content),
@@ -69,6 +73,23 @@ vi.mock('node:fs', async (importOriginal) => {
69
73
  };
70
74
  });
71
75
 
76
+ // ---------------------------------------------------------------------------
77
+ // Mock @cleocode/caamp for install provider tests
78
+ // ---------------------------------------------------------------------------
79
+
80
+ vi.mock('@cleocode/caamp', async (importOriginal) => {
81
+ const actual = await importOriginal<typeof import('@cleocode/caamp')>();
82
+ return {
83
+ ...actual,
84
+ ensureProviderInstructionFile: vi.fn(async () => ({
85
+ filePath: '/tmp/project/AGENTS.md',
86
+ instructFile: 'AGENTS.md',
87
+ action: mockCaampState.action,
88
+ providerId: 'openai-sdk',
89
+ })),
90
+ };
91
+ });
92
+
72
93
  // ---------------------------------------------------------------------------
73
94
  // Mock cant-context for spawn tests
74
95
  // ---------------------------------------------------------------------------
@@ -558,6 +579,7 @@ describe('OpenAiSdkInstallProvider', () => {
558
579
  installProvider = new OpenAiSdkInstallProvider();
559
580
  mockFsState.exists = false;
560
581
  mockFsState.content = '';
582
+ mockCaampState.action = 'created';
561
583
  });
562
584
 
563
585
  afterEach(() => vi.clearAllMocks());
@@ -577,14 +599,13 @@ describe('OpenAiSdkInstallProvider', () => {
577
599
  });
578
600
 
579
601
  it('marks instructionFileUpdated when AGENTS.md is created', async () => {
580
- mockFsState.exists = false;
602
+ mockCaampState.action = 'created';
581
603
  const result = await installProvider.install({ projectDir: '/tmp/project' });
582
604
  expect(result.instructionFileUpdated).toBe(true);
583
605
  });
584
606
 
585
- it('does not mark updated when references already present', async () => {
586
- mockFsState.exists = true;
587
- mockFsState.content = '@~/.cleo/templates/CLEO-INJECTION.md\n@.cleo/memory-bridge.md\n';
607
+ it('does not mark updated when references already present (intact)', async () => {
608
+ mockCaampState.action = 'intact';
588
609
  const result = await installProvider.install({ projectDir: '/tmp/project' });
589
610
  expect(result.instructionFileUpdated).toBe(false);
590
611
  });
@@ -2,24 +2,25 @@
2
2
  * OpenAI SDK Install Provider.
3
3
  *
4
4
  * Handles CLEO installation into OpenAI SDK environments:
5
- * - Writes an AGENTS.md file with CLEO @-references
5
+ * - Writes an AGENTS.md file with CLEO @-references (via CAAMP registry)
6
6
  * - Creates a `.openai/` config stub if it does not exist
7
7
  *
8
8
  * @task T582
9
+ * @task T9018
9
10
  */
10
11
 
11
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
12
+ import { existsSync, mkdirSync } from 'node:fs';
12
13
  import { join } from 'node:path';
14
+ import { ensureProviderInstructionFile } from '@cleocode/caamp';
13
15
  import type { AdapterInstallProvider, InstallOptions, InstallResult } from '@cleocode/contracts';
14
-
15
- /** Lines that should appear in AGENTS.md to reference CLEO. */
16
- const INSTRUCTION_REFERENCES = ['@~/.cleo/templates/CLEO-INJECTION.md', '@.cleo/memory-bridge.md'];
16
+ import { getCleoTemplatesTildePath } from '../shared/paths.js';
17
17
 
18
18
  /**
19
19
  * Install provider for the OpenAI SDK adapter (Vercel AI SDK).
20
20
  *
21
21
  * Manages CLEO's integration with OpenAI SDK projects by:
22
22
  * 1. Ensuring AGENTS.md contains @-references to CLEO instruction files
23
+ * (via CAAMP registry — single source of truth for instruction file name)
23
24
  * 2. Creating the `.openai/` config directory stub if absent
24
25
  *
25
26
  * @remarks
@@ -38,10 +39,15 @@ export class OpenAiSdkInstallProvider implements AdapterInstallProvider {
38
39
  const installedAt = new Date().toISOString();
39
40
  const details: Record<string, unknown> = {};
40
41
 
41
- // Step 1: Ensure AGENTS.md has @-references
42
- const instructionFileUpdated = this.updateInstructionFile(projectDir);
42
+ // Step 1: Ensure AGENTS.md has @-references via CAAMP registry
43
+ const instructionResult = await ensureProviderInstructionFile('openai-sdk', projectDir, {
44
+ references: [`@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`, '@.cleo/memory-bridge.md'],
45
+ scope: 'project',
46
+ });
47
+
48
+ const instructionFileUpdated = instructionResult.action !== 'intact';
43
49
  if (instructionFileUpdated) {
44
- details.instructionFile = join(projectDir, 'AGENTS.md');
50
+ details.instructionFile = instructionResult.filePath;
45
51
  }
46
52
 
47
53
  // Step 2: Create .openai config directory stub
@@ -68,7 +74,7 @@ export class OpenAiSdkInstallProvider implements AdapterInstallProvider {
68
74
  /**
69
75
  * Check whether CLEO is installed in the current OpenAI SDK environment.
70
76
  *
71
- * Checks for `@~/.cleo/templates/CLEO-INJECTION.md` in AGENTS.md.
77
+ * Checks for `@~/.local/share/cleo/templates/CLEO-INJECTION.md` in AGENTS.md.
72
78
  */
73
79
  async isInstalled(): Promise<boolean> {
74
80
  // A project is considered installed when AGENTS.md contains the first reference.
@@ -82,44 +88,16 @@ export class OpenAiSdkInstallProvider implements AdapterInstallProvider {
82
88
  * @param projectDir - Project root directory.
83
89
  */
84
90
  async ensureInstructionReferences(projectDir: string): Promise<void> {
85
- this.updateInstructionFile(projectDir);
91
+ await ensureProviderInstructionFile('openai-sdk', projectDir, {
92
+ references: [`@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`, '@.cleo/memory-bridge.md'],
93
+ scope: 'project',
94
+ });
86
95
  }
87
96
 
88
97
  // ---------------------------------------------------------------------------
89
98
  // Private helpers
90
99
  // ---------------------------------------------------------------------------
91
100
 
92
- /**
93
- * Update AGENTS.md with CLEO @-references.
94
- *
95
- * @returns `true` if the file was created or modified.
96
- */
97
- private updateInstructionFile(projectDir: string): boolean {
98
- const agentsMdPath = join(projectDir, 'AGENTS.md');
99
- let content = '';
100
- let existed = false;
101
-
102
- if (existsSync(agentsMdPath)) {
103
- content = readFileSync(agentsMdPath, 'utf-8');
104
- existed = true;
105
- }
106
-
107
- const missingRefs = INSTRUCTION_REFERENCES.filter((ref) => !content.includes(ref));
108
- if (missingRefs.length === 0) return false;
109
-
110
- const refsBlock = missingRefs.join('\n');
111
-
112
- if (existed) {
113
- const separator = content.endsWith('\n') ? '' : '\n';
114
- content = content + separator + refsBlock + '\n';
115
- } else {
116
- content = refsBlock + '\n';
117
- }
118
-
119
- writeFileSync(agentsMdPath, content, 'utf-8');
120
- return true;
121
- }
122
-
123
101
  /**
124
102
  * Create the `.openai/` config directory if it does not exist.
125
103
  *
@@ -54,6 +54,20 @@ vi.mock('node:fs/promises', () => ({
54
54
  unlink: vi.fn().mockResolvedValue(undefined),
55
55
  }));
56
56
 
57
+ // Mock CAAMP's ensureProviderInstructionFile to avoid registry I/O in unit tests.
58
+ vi.mock('@cleocode/caamp', async (importOriginal) => {
59
+ const actual = await importOriginal<typeof import('@cleocode/caamp')>();
60
+ return {
61
+ ...actual,
62
+ ensureProviderInstructionFile: vi.fn().mockResolvedValue({
63
+ filePath: '/tmp/test-project/AGENTS.md',
64
+ instructFile: 'AGENTS.md',
65
+ action: 'created',
66
+ providerId: 'opencode',
67
+ }),
68
+ };
69
+ });
70
+
57
71
  describe('OpenCodeAdapter', () => {
58
72
  let adapter: OpenCodeAdapter;
59
73
 
@@ -2,15 +2,17 @@
2
2
  * OpenCode Install Provider
3
3
  *
4
4
  * Handles CLEO installation into OpenCode environments:
5
- * - Ensures AGENTS.md has CLEO @-references
5
+ * - Ensures AGENTS.md has CLEO @-references via CAAMP
6
6
  * - Installs PreCompact hook shell shims + a JS plugin wrapper (T1013)
7
7
  *
8
8
  * @task T5240
9
9
  * @task T1013
10
+ * @task T9019
10
11
  */
11
12
 
12
13
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
13
14
  import { join } from 'node:path';
15
+ import { ensureProviderInstructionFile } from '@cleocode/caamp';
14
16
  import type { AdapterInstallProvider, InstallOptions, InstallResult } from '@cleocode/contracts';
15
17
  import {
16
18
  type InstallHookTemplatesResult,
@@ -18,26 +20,17 @@ import {
18
20
  } from '../shared/hook-template-installer.js';
19
21
  import { getCleoTemplatesTildePath } from '../shared/paths.js';
20
22
 
21
- /**
22
- * Lines that should appear in AGENTS.md to reference CLEO.
23
- * The CLEO-INJECTION.md path is resolved dynamically to support non-default
24
- * XDG / OS installation locations (T916).
25
- */
26
- const INSTRUCTION_REFERENCES = [
27
- `@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`,
28
- '@.cleo/memory-bridge.md',
29
- ];
30
-
31
23
  /**
32
24
  * Install provider for OpenCode.
33
25
  *
34
26
  * Manages CLEO's integration with OpenCode by:
35
27
  * 1. Ensuring AGENTS.md contains @-references to CLEO instruction files
28
+ * (delegated to CAAMP's canonical {@link ensureProviderInstructionFile}).
36
29
  * 2. Installing PreCompact hook shell templates + generating the JS plugin
37
30
  * wrapper that spawns the shim on `experimental.session.compacting` (T1013).
38
31
  *
39
32
  * @remarks
40
- * Installation is idempotent -- running install multiple times on the same
33
+ * Installation is idempotent running install multiple times on the same
41
34
  * project produces the same result. OpenCode's plugin system is the native
42
35
  * hook surface (OpenCode has no config-file hook registry like Claude Code or
43
36
  * Cursor), so the installer writes a JS plugin that subscribes to the native
@@ -55,13 +48,17 @@ export class OpenCodeInstallProvider implements AdapterInstallProvider {
55
48
  async install(options: InstallOptions): Promise<InstallResult> {
56
49
  const { projectDir } = options;
57
50
  const installedAt = new Date().toISOString();
58
- let instructionFileUpdated = false;
59
51
  const details: Record<string, unknown> = {};
60
52
 
61
- // Step 1: Ensure AGENTS.md has @-references
62
- instructionFileUpdated = this.updateInstructionFile(projectDir);
53
+ // Step 1: Ensure AGENTS.md has @-references via CAAMP canonical API.
54
+ const instructResult = await ensureProviderInstructionFile('opencode', projectDir, {
55
+ scope: 'project',
56
+ references: [`@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`, '@.cleo/memory-bridge.md'],
57
+ });
58
+
59
+ const instructionFileUpdated = instructResult.action !== 'intact';
63
60
  if (instructionFileUpdated) {
64
- details.instructionFile = join(projectDir, 'AGENTS.md');
61
+ details.instructionFile = join(projectDir, instructResult.instructFile);
65
62
  }
66
63
 
67
64
  // Step 2 (T1013): Install PreCompact hook templates + generate the JS
@@ -90,69 +87,35 @@ export class OpenCodeInstallProvider implements AdapterInstallProvider {
90
87
  /**
91
88
  * Check whether CLEO is installed in the current environment.
92
89
  *
93
- * Checks for CLEO references in AGENTS.md.
90
+ * Delegates to CAAMP's instruction-file check.
94
91
  */
95
92
  async isInstalled(): Promise<boolean> {
96
- const agentsMdPath = join(process.cwd(), 'AGENTS.md');
97
- if (existsSync(agentsMdPath)) {
98
- try {
99
- const content = readFileSync(agentsMdPath, 'utf-8');
100
- if (INSTRUCTION_REFERENCES.some((ref) => content.includes(ref))) {
101
- return true;
102
- }
103
- } catch {
104
- // Fall through
105
- }
93
+ try {
94
+ const result = await ensureProviderInstructionFile('opencode', process.cwd(), {
95
+ scope: 'project',
96
+ references: [
97
+ `@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`,
98
+ '@.cleo/memory-bridge.md',
99
+ ],
100
+ });
101
+ return result.action === 'intact';
102
+ } catch {
103
+ return false;
106
104
  }
107
-
108
- return false;
109
105
  }
110
106
 
111
107
  /**
112
108
  * Ensure AGENTS.md contains @-references to CLEO instruction files.
113
109
  *
114
- * Creates AGENTS.md if it does not exist. Appends any missing references.
110
+ * Delegates to CAAMP's canonical {@link ensureProviderInstructionFile}.
115
111
  *
116
112
  * @param projectDir - Project root directory
117
113
  */
118
114
  async ensureInstructionReferences(projectDir: string): Promise<void> {
119
- this.updateInstructionFile(projectDir);
120
- }
121
-
122
- /**
123
- * Update AGENTS.md with CLEO @-references.
124
- *
125
- * @returns true if the file was created or modified
126
- */
127
- private updateInstructionFile(projectDir: string): boolean {
128
- const agentsMdPath = join(projectDir, 'AGENTS.md');
129
- let content = '';
130
- let existed = false;
131
-
132
- if (existsSync(agentsMdPath)) {
133
- content = readFileSync(agentsMdPath, 'utf-8');
134
- existed = true;
135
- }
136
-
137
- const missingRefs = INSTRUCTION_REFERENCES.filter((ref) => !content.includes(ref));
138
-
139
- if (missingRefs.length === 0) {
140
- return false;
141
- }
142
-
143
- const refsBlock = missingRefs.join('\n');
144
-
145
- if (existed) {
146
- // Append missing references
147
- const separator = content.endsWith('\n') ? '' : '\n';
148
- content = content + separator + refsBlock + '\n';
149
- } else {
150
- // Create new AGENTS.md with references
151
- content = refsBlock + '\n';
152
- }
153
-
154
- writeFileSync(agentsMdPath, content, 'utf-8');
155
- return true;
115
+ await ensureProviderInstructionFile('opencode', projectDir, {
116
+ scope: 'project',
117
+ references: [`@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`, '@.cleo/memory-bridge.md'],
118
+ });
156
119
  }
157
120
 
158
121
  /**