Skip to content

Commit 9e6470b

Browse files
committed
fix(init): Include AGENTS guidance in non-interactive JSON
Capture AGENTS guidance outcomes during non-interactive init and embed the status in the emitted JSON report. Include path and error details when AGENTS updates fail so automation can reason about partial success without parsing stderr. Preserve existing failure behavior by throwing after JSON emission on AGENTS errors, and extend init command tests to cover created, updated, and error statuses in JSON output.
1 parent cc1ead7 commit 9e6470b

2 files changed

Lines changed: 62 additions & 2 deletions

File tree

src/cli/commands/__tests__/init.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,10 @@ describe('init command', () => {
467467
const output = parseJsonOutput(stdoutSpy);
468468
expect(output.action).toBe('install');
469469
expect(output.message).toBe('Installed XcodeBuildMCP CLI skill');
470+
expect(output.agentsGuidance).toEqual({
471+
status: 'created',
472+
path: agentsPath,
473+
});
470474

471475
stdoutSpy.mockRestore();
472476
});
@@ -495,6 +499,12 @@ describe('init command', () => {
495499
const output = parseJsonOutput(stdoutSpy);
496500
expect(output.action).toBe('install');
497501
expect(output.message).toBe('Installed XcodeBuildMCP CLI skill');
502+
expect(output.agentsGuidance).toEqual({
503+
status: 'error',
504+
path: join(projectRoot, 'AGENTS.md'),
505+
error:
506+
'AGENTS.md exists and requires confirmation to update. Re-run with --force to apply the change in non-interactive mode.',
507+
});
498508

499509
stdoutSpy.mockRestore();
500510
Object.defineProperty(process.stdin, 'isTTY', { value: originalIsTTY, configurable: true });
@@ -526,6 +536,10 @@ describe('init command', () => {
526536
const output = parseJsonOutput(stdoutSpy);
527537
expect(output.action).toBe('install');
528538
expect(output.message).toBe('Installed XcodeBuildMCP CLI skill');
539+
expect(output.agentsGuidance).toEqual({
540+
status: 'updated',
541+
path: join(projectRoot, 'AGENTS.md'),
542+
});
529543

530544
stdoutSpy.mockRestore();
531545
Object.defineProperty(process.stdin, 'isTTY', { value: originalIsTTY, configurable: true });
@@ -560,6 +574,14 @@ describe('init command', () => {
560574
)?.length,
561575
).toBe(1);
562576

577+
const output = parseJsonOutput(stdoutSpy);
578+
expect(output.action).toBe('install');
579+
expect(output.message).toBe('Installed XcodeBuildMCP CLI skill');
580+
expect(output.agentsGuidance).toEqual({
581+
status: 'updated',
582+
path: join(projectRoot, 'AGENTS.md'),
583+
});
584+
563585
stdoutSpy.mockRestore();
564586
});
565587
});

src/cli/commands/init.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,19 @@ function formatSkippedClients(skippedClients: Array<{ client: string; reason: st
124124
return skippedClients.map((skipped) => `${skipped.client}: ${skipped.reason}`).join('; ');
125125
}
126126

127+
type AgentsGuidanceStatus = 'created' | 'updated' | 'no_change' | 'skipped' | 'error';
128+
127129
interface InitReport {
128130
action: 'install' | 'uninstall';
129131
skillType?: SkillType;
130132
installed?: InstallResult[];
131133
removed?: Array<{ client: string; variant: string; path: string }>;
132134
skipped?: Array<{ client: string; reason: string }>;
135+
agentsGuidance?: {
136+
status: AgentsGuidanceStatus;
137+
path: string;
138+
error?: string;
139+
};
133140
message: string;
134141
}
135142

@@ -595,11 +602,38 @@ export function registerInitCommand(app: Argv, ctx?: { workspaceRoot: string }):
595602
results.push(result);
596603
}
597604

605+
let agentsGuidanceStatus: AgentsGuidanceStatus | undefined;
606+
let agentsGuidancePath: string | undefined;
607+
let agentsGuidanceError: string | undefined;
608+
if (!isTTY && ctx?.workspaceRoot) {
609+
const projectRoot = path.resolve(ctx.workspaceRoot);
610+
agentsGuidancePath = path.join(projectRoot, AGENTS_FILE_NAME);
611+
try {
612+
agentsGuidanceStatus = await ensureAgentsGuidance(
613+
projectRoot,
614+
argv.force as boolean,
615+
false,
616+
);
617+
} catch (error) {
618+
agentsGuidanceStatus = 'error';
619+
agentsGuidanceError = error instanceof Error ? error.message : String(error);
620+
}
621+
}
622+
598623
const report: InitReport = {
599624
action: 'install',
600625
skillType: selection.skillType,
601626
installed: results,
602627
skipped: policy.skippedClients,
628+
...(agentsGuidanceStatus && agentsGuidancePath
629+
? {
630+
agentsGuidance: {
631+
status: agentsGuidanceStatus,
632+
path: agentsGuidancePath,
633+
...(agentsGuidanceError ? { error: agentsGuidanceError } : {}),
634+
},
635+
}
636+
: {}),
603637
message: `Installed ${skillDisplayName(selection.skillType)} skill`,
604638
};
605639

@@ -616,9 +650,13 @@ export function registerInitCommand(app: Argv, ctx?: { workspaceRoot: string }):
616650
process.stdout.write(`${JSON.stringify(report)}\n`);
617651
}
618652

619-
if (ctx?.workspaceRoot) {
653+
if (agentsGuidanceStatus === 'error' && agentsGuidanceError) {
654+
throw new Error(agentsGuidanceError);
655+
}
656+
657+
if (ctx?.workspaceRoot && isTTY) {
620658
const projectRoot = path.resolve(ctx.workspaceRoot);
621-
await ensureAgentsGuidance(projectRoot, argv.force as boolean, isTTY);
659+
await ensureAgentsGuidance(projectRoot, argv.force as boolean, true);
622660
}
623661
},
624662
);

0 commit comments

Comments
 (0)