@@ -29,6 +29,11 @@ function loadInitModule() {
2929 return import ( '../init.ts' ) ;
3030}
3131
32+ function parseJsonOutput ( stdoutSpy : { mock : { calls : unknown [ ] [ ] } } ) : Record < string , unknown > {
33+ const output = stdoutSpy . mock . calls . map ( ( call ) => String ( call [ 0 ] ?? '' ) ) . join ( '' ) ;
34+ return JSON . parse ( output . trim ( ) ) as Record < string , unknown > ;
35+ }
36+
3237describe ( 'init command' , ( ) => {
3338 let tempDir : string ;
3439 let fakeResourceRoot : string ;
@@ -81,10 +86,11 @@ describe('init command', () => {
8186 expect ( existsSync ( installed ) ) . toBe ( true ) ;
8287 expect ( readFileSync ( installed , 'utf8' ) ) . toBe ( '# CLI Skill Content' ) ;
8388
84- const output = stdoutSpy . mock . calls . map ( ( c ) => String ( c [ 0 ] ) ) . join ( '' ) ;
85- expect ( output ) . toContain ( 'Installed XcodeBuildMCP CLI skill' ) ;
86- expect ( output ) . toContain ( 'Custom' ) ;
87- expect ( output ) . toContain ( installed ) ;
89+ const output = parseJsonOutput ( stdoutSpy ) ;
90+ expect ( output . action ) . toBe ( 'install' ) ;
91+ expect ( output . skillType ) . toBe ( 'cli' ) ;
92+ expect ( output . message ) . toBe ( 'Installed XcodeBuildMCP CLI skill' ) ;
93+ expect ( output . installed ) . toEqual ( [ { client : 'Custom' , location : installed } ] ) ;
8894
8995 stdoutSpy . mockRestore ( ) ;
9096 } ) ;
@@ -106,8 +112,10 @@ describe('init command', () => {
106112 expect ( existsSync ( installed ) ) . toBe ( true ) ;
107113 expect ( readFileSync ( installed , 'utf8' ) ) . toBe ( '# MCP Skill Content' ) ;
108114
109- const output = stdoutSpy . mock . calls . map ( ( c ) => String ( c [ 0 ] ) ) . join ( '' ) ;
110- expect ( output ) . toContain ( 'Installed XcodeBuildMCP (MCP server) skill' ) ;
115+ const output = parseJsonOutput ( stdoutSpy ) ;
116+ expect ( output . action ) . toBe ( 'install' ) ;
117+ expect ( output . skillType ) . toBe ( 'mcp' ) ;
118+ expect ( output . message ) . toBe ( 'Installed XcodeBuildMCP (MCP server) skill' ) ;
111119
112120 stdoutSpy . mockRestore ( ) ;
113121 } ) ;
@@ -173,8 +181,15 @@ describe('init command', () => {
173181 true ,
174182 ) ;
175183
176- const output = stdoutSpy . mock . calls . map ( ( c ) => String ( c [ 0 ] ) ) . join ( '' ) ;
177- expect ( output ) . toContain ( 'Skipped Claude Code' ) ;
184+ const output = parseJsonOutput ( stdoutSpy ) ;
185+ expect ( output . action ) . toBe ( 'install' ) ;
186+ expect ( output . skillType ) . toBe ( 'mcp' ) ;
187+ expect ( output . skipped ) . toEqual ( [
188+ {
189+ client : 'Claude Code' ,
190+ reason : 'MCP skill is unnecessary because Claude Code already uses server instructions.' ,
191+ } ,
192+ ] ) ;
178193
179194 stdoutSpy . mockRestore ( ) ;
180195 } ) ;
@@ -317,10 +332,16 @@ describe('init command', () => {
317332 expect ( existsSync ( cliSkillDir ) ) . toBe ( false ) ;
318333 expect ( existsSync ( mcpSkillDir ) ) . toBe ( false ) ;
319334
320- const output = stdoutSpy . mock . calls . map ( ( c ) => String ( c [ 0 ] ) ) . join ( '' ) ;
321- expect ( output ) . toContain ( 'Uninstalled skill directories' ) ;
322- expect ( output ) . toContain ( 'Removed (xcodebuildmcp-cli):' ) ;
323- expect ( output ) . toContain ( 'Removed (xcodebuildmcp):' ) ;
335+ const output = parseJsonOutput ( stdoutSpy ) ;
336+ expect ( output . action ) . toBe ( 'uninstall' ) ;
337+ expect ( output . message ) . toBe ( 'Uninstalled skill directories' ) ;
338+ expect ( output . removed ) . toHaveLength ( 2 ) ;
339+ expect ( output . removed ) . toEqual (
340+ expect . arrayContaining ( [
341+ { client : 'Custom' , variant : 'xcodebuildmcp-cli' , path : cliSkillDir } ,
342+ { client : 'Custom' , variant : 'xcodebuildmcp' , path : mcpSkillDir } ,
343+ ] ) ,
344+ ) ;
324345
325346 stdoutSpy . mockRestore ( ) ;
326347 } ) ;
@@ -338,8 +359,10 @@ describe('init command', () => {
338359 const stdoutSpy = vi . spyOn ( process . stdout , 'write' ) . mockImplementation ( ( ) => true ) ;
339360 await app . parseAsync ( ) ;
340361
341- const output = stdoutSpy . mock . calls . map ( ( c ) => String ( c [ 0 ] ) ) . join ( '' ) ;
342- expect ( output ) . toContain ( 'No installed skill directories found' ) ;
362+ const output = parseJsonOutput ( stdoutSpy ) ;
363+ expect ( output . action ) . toBe ( 'uninstall' ) ;
364+ expect ( output . message ) . toBe ( 'No installed skill directories found to remove.' ) ;
365+ expect ( output . removed ) . toEqual ( [ ] ) ;
343366
344367 stdoutSpy . mockRestore ( ) ;
345368 } ) ;
@@ -358,8 +381,10 @@ describe('init command', () => {
358381 const stdoutSpy = vi . spyOn ( process . stdout , 'write' ) . mockImplementation ( ( ) => true ) ;
359382 await app . parseAsync ( ) ;
360383
361- const output = stdoutSpy . mock . calls . map ( ( c ) => String ( c [ 0 ] ) ) . join ( '' ) ;
362- expect ( output ) . toContain ( 'No installed skill directories found' ) ;
384+ const output = parseJsonOutput ( stdoutSpy ) ;
385+ expect ( output . action ) . toBe ( 'uninstall' ) ;
386+ expect ( output . message ) . toBe ( 'No installed skill directories found to remove.' ) ;
387+ expect ( output . removed ) . toEqual ( [ ] ) ;
363388
364389 stdoutSpy . mockRestore ( ) ;
365390 } ) ;
@@ -439,8 +464,9 @@ describe('init command', () => {
439464 expect ( existsSync ( agentsPath ) ) . toBe ( true ) ;
440465 expect ( readFileSync ( agentsPath , 'utf8' ) ) . toContain ( agentsGuidanceLine ) ;
441466
442- const output = stdoutSpy . mock . calls . map ( ( c ) => String ( c [ 0 ] ) ) . join ( '' ) ;
443- expect ( output ) . toContain ( 'Created AGENTS.md with XcodeBuildMCP guidance' ) ;
467+ const output = parseJsonOutput ( stdoutSpy ) ;
468+ expect ( output . action ) . toBe ( 'install' ) ;
469+ expect ( output . message ) . toBe ( 'Installed XcodeBuildMCP CLI skill' ) ;
444470
445471 stdoutSpy . mockRestore ( ) ;
446472 } ) ;
@@ -466,11 +492,9 @@ describe('init command', () => {
466492 'AGENTS.md exists and requires confirmation to update' ,
467493 ) ;
468494
469- const output = stdoutSpy . mock . calls . map ( ( c ) => String ( c [ 0 ] ) ) . join ( '' ) ;
470- expect ( output ) . toContain ( 'Proposed update for' ) ;
471- expect ( output ) . toContain ( '--- AGENTS.md' ) ;
472- expect ( output ) . toContain ( '+++ AGENTS.md' ) ;
473- expect ( output ) . toContain ( agentsGuidanceLine ) ;
495+ const output = parseJsonOutput ( stdoutSpy ) ;
496+ expect ( output . action ) . toBe ( 'install' ) ;
497+ expect ( output . message ) . toBe ( 'Installed XcodeBuildMCP CLI skill' ) ;
474498
475499 stdoutSpy . mockRestore ( ) ;
476500 Object . defineProperty ( process . stdin , 'isTTY' , { value : originalIsTTY , configurable : true } ) ;
@@ -499,9 +523,9 @@ describe('init command', () => {
499523 expect ( agentsContent ) . toContain ( '# Existing' ) ;
500524 expect ( agentsContent ) . toContain ( agentsGuidanceLine ) ;
501525
502- const output = stdoutSpy . mock . calls . map ( ( c ) => String ( c [ 0 ] ) ) . join ( '' ) ;
503- expect ( output ) . toContain ( 'Proposed update for ') ;
504- expect ( output ) . toContain ( 'Updated AGENTS.md at ') ;
526+ const output = parseJsonOutput ( stdoutSpy ) ;
527+ expect ( output . action ) . toBe ( 'install ') ;
528+ expect ( output . message ) . toBe ( 'Installed XcodeBuildMCP CLI skill ') ;
505529
506530 stdoutSpy . mockRestore ( ) ;
507531 Object . defineProperty ( process . stdin , 'isTTY' , { value : originalIsTTY , configurable : true } ) ;
0 commit comments