@@ -5,7 +5,11 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
55import {
66 installKairoHooks ,
77 mergeKairoHooks ,
8+ mergeKairoMcpConfig ,
9+ mergeManagedText ,
810 removeKairoHooks ,
11+ removeKairoMcpConfig ,
12+ removeManagedText ,
913 uninstallKairoHooks ,
1014} from "./merge-hooks.ts" ;
1115
@@ -103,18 +107,70 @@ describe("mergeKairoHooks", () => {
103107 } ) ;
104108} ) ;
105109
110+ describe ( "mergeKairoMcpConfig" , ( ) => {
111+ it ( "preserves existing MCP servers and installs Kairo" , ( ) => {
112+ expect (
113+ mergeKairoMcpConfig (
114+ { mcpServers : { custom : { command : "custom-mcp" } } } ,
115+ { mcpServers : { kairo : { command : "kairo-mcp" , args : [ ] , enabled : true } } } ,
116+ ) ,
117+ ) . toEqual ( {
118+ mcpServers : {
119+ custom : { command : "custom-mcp" } ,
120+ kairo : { command : "kairo-mcp" , args : [ ] , enabled : true } ,
121+ } ,
122+ } ) ;
123+ } ) ;
124+ } ) ;
125+
126+ describe ( "managed text sections" , ( ) => {
127+ const marker = {
128+ start : "<!-- KAIRO:TEST:START -->" ,
129+ end : "<!-- KAIRO:TEST:END -->" ,
130+ } ;
131+ const template = "<!-- KAIRO:TEST:START -->\nmanaged\n<!-- KAIRO:TEST:END -->\n" ;
132+
133+ it ( "appends and refreshes one managed section" , ( ) => {
134+ const once = mergeManagedText ( "# Existing\n" , template , marker ) ;
135+ const twice = mergeManagedText ( once , template . replace ( "managed" , "refreshed" ) , marker ) ;
136+
137+ expect ( once ) . toContain ( "# Existing" ) ;
138+ expect ( twice . match ( / K A I R O : T E S T : S T A R T / g) ) . toHaveLength ( 1 ) ;
139+ expect ( twice ) . toContain ( "refreshed" ) ;
140+ expect ( twice ) . not . toContain ( "managed\n" ) ;
141+ } ) ;
142+
143+ it ( "removes only the managed section" , ( ) => {
144+ const merged = mergeManagedText ( "# Existing\n" , template , marker ) ;
145+
146+ expect ( removeManagedText ( merged , marker ) ) . toBe ( "# Existing" ) ;
147+ } ) ;
148+ } ) ;
149+
106150describe ( "installKairoHooks" , ( ) => {
107- it ( "reads templates from disk and writes idempotent Claude and Codex hook files" , ( ) => {
151+ it ( "reads templates from disk and writes idempotent agent integration files" , ( ) => {
108152 installKairoHooks ( projectRoot ) ;
109153 const claudePath = join ( projectRoot , ".claude" , "hooks.json" ) ;
110154 const codexPath = join ( projectRoot , ".codex" , "hooks.json" ) ;
155+ const mcpPath = join ( projectRoot , ".mcp.json" ) ;
156+ const codexConfigPath = join ( projectRoot , ".codex" , "config.toml" ) ;
157+ const agentsPath = join ( projectRoot , "AGENTS.md" ) ;
158+ const claudeGuidePath = join ( projectRoot , "CLAUDE.md" ) ;
111159 const firstClaude = readFileSync ( claudePath , "utf8" ) ;
112160 const firstCodex = readFileSync ( codexPath , "utf8" ) ;
161+ const firstMcp = readFileSync ( mcpPath , "utf8" ) ;
162+ const firstCodexConfig = readFileSync ( codexConfigPath , "utf8" ) ;
163+ const firstAgents = readFileSync ( agentsPath , "utf8" ) ;
164+ const firstClaudeGuide = readFileSync ( claudeGuidePath , "utf8" ) ;
113165
114166 installKairoHooks ( projectRoot ) ;
115167
116168 expect ( readFileSync ( claudePath , "utf8" ) ) . toBe ( firstClaude ) ;
117169 expect ( readFileSync ( codexPath , "utf8" ) ) . toBe ( firstCodex ) ;
170+ expect ( readFileSync ( mcpPath , "utf8" ) ) . toBe ( firstMcp ) ;
171+ expect ( readFileSync ( codexConfigPath , "utf8" ) ) . toBe ( firstCodexConfig ) ;
172+ expect ( readFileSync ( agentsPath , "utf8" ) ) . toBe ( firstAgents ) ;
173+ expect ( readFileSync ( claudeGuidePath , "utf8" ) ) . toBe ( firstClaudeGuide ) ;
118174 const claude = JSON . parse ( firstClaude ) ;
119175 expect ( claude . hooks . PostToolUse [ 0 ] . kairo ) . toBe ( true ) ;
120176 expect ( claude . hooks . PreCompact [ 0 ] ) . toMatchObject ( {
@@ -126,6 +182,13 @@ describe("installKairoHooks", () => {
126182 hooks : [ { command : "kairo wake --days 7" } ] ,
127183 } ) ;
128184 expect ( JSON . parse ( firstCodex ) . hooks [ 0 ] . kairo ) . toBe ( true ) ;
185+ expect ( JSON . parse ( firstMcp ) . mcpServers . kairo ) . toMatchObject ( {
186+ command : "kairo-mcp" ,
187+ enabled : true ,
188+ } ) ;
189+ expect ( firstCodexConfig ) . toContain ( "[mcp_servers.kairo]" ) ;
190+ expect ( firstAgents ) . toContain ( "Kairo Project Memory" ) ;
191+ expect ( firstClaudeGuide ) . toContain ( "Kairo Project Memory" ) ;
129192 } ) ;
130193
131194 it ( "preserves pre-existing non-Kairo hooks when installing" , ( ) => {
@@ -151,6 +214,23 @@ describe("installKairoHooks", () => {
151214 } ) ;
152215} ) ;
153216
217+ describe ( "removeKairoMcpConfig" , ( ) => {
218+ it ( "removes only the Kairo MCP server" , ( ) => {
219+ expect (
220+ removeKairoMcpConfig ( {
221+ mcpServers : {
222+ custom : { command : "custom" } ,
223+ kairo : { command : "kairo-mcp" } ,
224+ } ,
225+ } ) ,
226+ ) . toEqual ( {
227+ mcpServers : {
228+ custom : { command : "custom" } ,
229+ } ,
230+ } ) ;
231+ } ) ;
232+ } ) ;
233+
154234describe ( "removeKairoHooks" , ( ) => {
155235 it ( "removes only tagged Claude hooks" , ( ) => {
156236 expect (
@@ -194,15 +274,21 @@ describe("uninstallKairoHooks", () => {
194274 installKairoHooks ( projectRoot ) ;
195275 const claudePath = join ( projectRoot , ".claude" , "hooks.json" ) ;
196276 const codexPath = join ( projectRoot , ".codex" , "hooks.json" ) ;
277+ const mcpPath = join ( projectRoot , ".mcp.json" ) ;
278+ const codexConfigPath = join ( projectRoot , ".codex" , "config.toml" ) ;
279+ const agentsPath = join ( projectRoot , "AGENTS.md" ) ;
197280 const claudeWithCustom = JSON . parse ( readFileSync ( claudePath , "utf8" ) ) ;
198281 const codexWithCustom = JSON . parse ( readFileSync ( codexPath , "utf8" ) ) ;
282+ const mcpWithCustom = JSON . parse ( readFileSync ( mcpPath , "utf8" ) ) ;
199283 claudeWithCustom . hooks . PostToolUse . unshift ( {
200284 matcher : "Write" ,
201285 hooks : [ { type : "command" , command : "custom" } ] ,
202286 } ) ;
203287 codexWithCustom . hooks . unshift ( { event : "post_edit" , command : "custom" } ) ;
288+ mcpWithCustom . mcpServers . custom = { command : "custom" } ;
204289 writeFileSync ( claudePath , JSON . stringify ( claudeWithCustom ) ) ;
205290 writeFileSync ( codexPath , JSON . stringify ( codexWithCustom ) ) ;
291+ writeFileSync ( mcpPath , JSON . stringify ( mcpWithCustom ) ) ;
206292
207293 uninstallKairoHooks ( projectRoot ) ;
208294
@@ -212,5 +298,9 @@ describe("uninstallKairoHooks", () => {
212298 expect ( JSON . stringify ( codex ) ) . toContain ( "custom" ) ;
213299 expect ( JSON . stringify ( claude ) ) . not . toContain ( '"kairo":true' ) ;
214300 expect ( JSON . stringify ( codex ) ) . not . toContain ( '"kairo":true' ) ;
301+ expect ( readFileSync ( mcpPath , "utf8" ) ) . toContain ( "custom" ) ;
302+ expect ( readFileSync ( mcpPath , "utf8" ) ) . not . toContain ( "kairo-mcp" ) ;
303+ expect ( readFileSync ( codexConfigPath , "utf8" ) ) . not . toContain ( "[mcp_servers.kairo]" ) ;
304+ expect ( readFileSync ( agentsPath , "utf8" ) ) . not . toContain ( "Kairo Project Memory" ) ;
215305 } ) ;
216306} ) ;
0 commit comments