Skip to content

Commit 24d397b

Browse files
author
Marvin Zhang
committed
feat(init): implement AI-assisted auto-merge for AGENTS.md using detected CLI tools
1 parent ec86835 commit 24d397b

File tree

3 files changed

+638
-4
lines changed

3 files changed

+638
-4
lines changed

packages/cli/src/commands/init.ts

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as path from 'node:path';
33
import { fileURLToPath } from 'node:url';
44
import chalk from 'chalk';
55
import { Command } from 'commander';
6-
import { select, checkbox } from '@inquirer/prompts';
6+
import { select, checkbox, confirm } from '@inquirer/prompts';
77
import { saveConfig, type LeanSpecConfig } from '../config.js';
88
import {
99
detectExistingSystemPrompts,
@@ -13,6 +13,9 @@ import {
1313
createAgentToolSymlinks,
1414
AI_TOOL_CONFIGS,
1515
getDefaultAIToolSelection,
16+
getCliCapableDetectedTools,
17+
executeMergeWithAI,
18+
getDisplayCommand,
1619
type AIToolKey,
1720
} from '../utils/template-helpers.js';
1821
import {
@@ -26,6 +29,72 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
2629
const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
2730
const EXAMPLES_DIR = path.join(TEMPLATES_DIR, 'examples');
2831

32+
/**
33+
* Attempt to auto-merge AGENTS.md using detected AI CLI tool
34+
* Returns true if merge was successfully completed
35+
*/
36+
async function attemptAutoMerge(cwd: string, promptPath: string, autoExecute: boolean): Promise<boolean> {
37+
// Check for CLI-capable AI tools
38+
const cliTools = await getCliCapableDetectedTools();
39+
40+
if (cliTools.length === 0) {
41+
// No CLI tools detected, fall back to manual instructions
42+
return false;
43+
}
44+
45+
// Use first detected CLI-capable tool
46+
const tool = cliTools[0];
47+
const displayCmd = getDisplayCommand(tool.tool, promptPath);
48+
49+
console.log('');
50+
console.log(chalk.cyan(`🔍 Detected AI CLI: ${tool.config.description}`));
51+
for (const reason of tool.reasons) {
52+
console.log(chalk.gray(` └─ ${reason}`));
53+
}
54+
console.log('');
55+
console.log(chalk.gray(`Command: ${displayCmd}`));
56+
console.log('');
57+
58+
let shouldExecute = autoExecute;
59+
60+
if (!autoExecute) {
61+
shouldExecute = await confirm({
62+
message: 'Run merge automatically using detected AI CLI?',
63+
default: true,
64+
});
65+
}
66+
67+
if (!shouldExecute) {
68+
console.log(chalk.gray('Skipping auto-merge. Run the command above manually to merge.'));
69+
return false;
70+
}
71+
72+
console.log('');
73+
console.log(chalk.cyan('🤖 Running AI-assisted merge...'));
74+
console.log(chalk.gray(' (This may take a moment)'));
75+
console.log('');
76+
77+
const result = await executeMergeWithAI(cwd, promptPath, tool.tool);
78+
79+
if (result.success) {
80+
console.log('');
81+
console.log(chalk.green('✓ AGENTS.md merged successfully!'));
82+
console.log(chalk.gray(' Review changes: git diff AGENTS.md'));
83+
return true;
84+
} else if (result.timedOut) {
85+
console.log('');
86+
console.log(chalk.yellow('⚠ Merge timed out. Try running the command manually:'));
87+
console.log(chalk.gray(` ${displayCmd}`));
88+
return false;
89+
} else {
90+
console.log('');
91+
console.log(chalk.yellow(`⚠ Auto-merge encountered an issue: ${result.error}`));
92+
console.log(chalk.gray(' Try running the command manually:'));
93+
console.log(chalk.gray(` ${displayCmd}`));
94+
return false;
95+
}
96+
}
97+
2998
/**
3099
* Init command - initialize LeanSpec in current directory
31100
*/
@@ -307,6 +376,7 @@ export async function initProject(skipPrompts = false, templateOption?: string,
307376
// Check for existing system prompt files
308377
const existingFiles = await detectExistingSystemPrompts(cwd);
309378
let skipFiles: string[] = [];
379+
let mergeCompleted = false;
310380

311381
if (existingFiles.length > 0) {
312382
console.log('');
@@ -317,6 +387,10 @@ export async function initProject(skipPrompts = false, templateOption?: string,
317387
console.log(chalk.gray('Using AI-Assisted Merge for existing AGENTS.md'));
318388
const projectName = await getProjectName(cwd);
319389
await handleExistingFiles('merge-ai', existingFiles, templateDir, cwd, { project_name: projectName });
390+
391+
// Auto-execute merge if CLI tool is available
392+
const promptPath = path.join(cwd, '.lean-spec', 'MERGE-AGENTS-PROMPT.md');
393+
mergeCompleted = await attemptAutoMerge(cwd, promptPath, true /* skipPrompts */);
320394
} else {
321395
const action = await select<'merge-ai' | 'merge-append' | 'overwrite' | 'skip'>({
322396
message: 'How would you like to handle existing AGENTS.md?',
@@ -351,15 +425,19 @@ export async function initProject(skipPrompts = false, templateOption?: string,
351425

352426
if (action === 'skip') {
353427
skipFiles = existingFiles;
428+
} else if (action === 'merge-ai') {
429+
// Offer auto-merge if CLI tool is available
430+
const promptPath = path.join(cwd, '.lean-spec', 'MERGE-AGENTS-PROMPT.md');
431+
mergeCompleted = await attemptAutoMerge(cwd, promptPath, false /* skipPrompts */);
354432
}
355433
}
356434
}
357435

358436
// Get project name for variable substitution
359437
const projectName = await getProjectName(cwd);
360438

361-
// Copy AGENTS.md from template root to project root (unless skipping)
362-
if (!skipFiles.includes('AGENTS.md')) {
439+
// Copy AGENTS.md from template root to project root (unless skipping or already merged)
440+
if (!skipFiles.includes('AGENTS.md') && !mergeCompleted) {
363441
const agentsSourcePath = path.join(templateDir, 'AGENTS.md');
364442
const agentsTargetPath = path.join(cwd, 'AGENTS.md');
365443

0 commit comments

Comments
 (0)