Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cb98ee9

Browse files
committed
refactor: extract agent command to separate module
- Move aiCommitsAgent function from aicommits.ts to agent.ts - Extract handleAgentMode and handleSplitMode functions to agent command - Reorganize imports and command structure for better separation of concerns - Add dev:agent script to package.json - Reorder ConfigService constructor parameters
1 parent 744c101 commit cb98ee9

File tree

6 files changed

+197
-125
lines changed

6 files changed

+197
-125
lines changed

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,19 @@
4747
"cq:prettier:fix": "prettier -w --ignore-unknown .",
4848
"cq:type-check": "tsc --noEmit --pretty --incremental --skipLibCheck",
4949
"dev": "tsx src/cli.ts",
50+
"dev:agent": "tsx src/cli.ts agent",
5051
"dev:setup": "tsx src/cli.ts setup",
5152
"dev:stage-all": "tsx src/cli.ts --stage-all",
53+
"install:link": "npm run build && npm link",
54+
"install:test": "npm run build && npm pack && npm install -g ./lucavb-aicommits-*.tgz && rm ./lucavb-aicommits-*.tgz",
5255
"prepack": "npm run build",
56+
"prepare": "husky",
5357
"release": "semantic-release",
5458
"release:dry-run": "semantic-release --dry-run",
5559
"test": "vitest run",
5660
"test:coverage": "vitest run --coverage",
5761
"test:watch": "vitest",
58-
"install:link": "npm run build && npm link",
59-
"install:test": "npm run build && npm pack && npm install -g ./lucavb-aicommits-*.tgz && rm ./lucavb-aicommits-*.tgz",
60-
"uninstall:test": "npm uninstall -g @lucavb/aicommits",
61-
"prepare": "husky"
62+
"uninstall:test": "npm uninstall -g @lucavb/aicommits"
6263
},
6364
"dependencies": {
6465
"@ai-sdk/anthropic": "2.0.5",

src/cli.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
import 'reflect-metadata';
22
import { Option, program } from '@commander-js/extra-typings';
33
import { aiCommits } from './commands/aicommits';
4+
import { agentCommand } from './commands/agent';
45
import { versionCommand } from './commands/version';
56
import { configCommand } from './commands/config';
67
import { setupCommand } from './commands/setup';
78
import { prepareCommitMsgCommand } from './commands/prepare-commit-msg';
8-
import { agentCommand } from './commands/agent';
99
import { ignoreCommand } from './commands/ignore';
1010
import { container } from './utils/di';
1111
import { CLI_ARGUMENTS } from './services/config.service';
1212

13+
program.addCommand(agentCommand);
1314
program.addCommand(configCommand);
14-
program.addCommand(setupCommand);
15-
program.addCommand(prepareCommitMsgCommand);
1615
program.addCommand(ignoreCommand);
16+
program.addCommand(prepareCommitMsgCommand);
17+
program.addCommand(setupCommand);
1718
program.addCommand(versionCommand);
18-
program.addCommand(agentCommand);
1919

2020
program
2121
.passThroughOptions(true)
@@ -33,8 +33,8 @@ program
3333
container.bind(CLI_ARGUMENTS).toConstantValue(options);
3434
await aiCommits({
3535
container,
36-
stageAll: options.stageAll,
3736
profile: options.profile,
37+
stageAll: options.stageAll,
3838
});
3939
});
4040

src/commands/agent.ts

Lines changed: 183 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,189 @@
11
import { Command, Option } from '@commander-js/extra-typings';
2-
import { aiCommits } from './aicommits';
2+
import { bgCyan, black, green, red, yellow } from 'kolorist';
3+
import { handleCliError, KnownError } from '../utils/error';
4+
import { isError } from '../utils/typeguards';
5+
import { Container } from 'inversify';
6+
import { AIAgentService } from '../services/ai-agent.service';
7+
import { AICommitMessageService } from '../services/ai-commit-message.service';
8+
import { AICommitSplittingService } from '../services/ai-commit-splitting.service';
9+
import { GitService } from '../services/git.service';
10+
import { ConfigService } from '../services/config.service';
11+
import { ClackPromptService } from '../services/clack-prompt.service';
12+
import { agentStreamingReviewAndRevise, handleCommitSplitting } from './aicommits-utils';
13+
import { trimLines } from '../utils/string';
314
import { container } from '../utils/di';
415
import { CLI_ARGUMENTS } from '../services/config.service';
516

17+
export const aiCommitsAgent = async ({
18+
container,
19+
stageAll = false,
20+
profile = 'default',
21+
splitMode = false,
22+
}: {
23+
container: Container;
24+
stageAll?: boolean;
25+
profile?: string;
26+
splitMode?: boolean;
27+
}) => {
28+
const configService = container.get(ConfigService);
29+
const gitService = container.get(GitService);
30+
const aiAgentService = container.get(AIAgentService);
31+
const aiCommitMessageService = container.get(AICommitMessageService);
32+
const aiCommitSplittingService = container.get(AICommitSplittingService);
33+
const promptUI = container.get(ClackPromptService);
34+
35+
try {
36+
await configService.readConfig();
37+
38+
promptUI.intro(bgCyan(black(' aicommits agent ')));
39+
const validResult = configService.validConfig();
40+
if (!validResult.valid) {
41+
promptUI.note(
42+
trimLines(`
43+
It looks like you haven't set up aicommits yet. Let's get you started!
44+
45+
Run ${yellow('aicommits setup')} to configure your settings.
46+
`),
47+
);
48+
process.exit(1);
49+
}
50+
51+
const currentProfile = configService.getProfile(profile);
52+
if (!currentProfile) {
53+
const config = configService.getProfileNames();
54+
promptUI.note(
55+
trimLines(`
56+
Profile "${profile}" not found. Available profiles: ${config.join(', ')}
57+
58+
Run ${yellow('aicommits setup --profile ' + profile)} to create this profile.
59+
`),
60+
);
61+
process.exit(1);
62+
}
63+
64+
const config = currentProfile;
65+
66+
// Display provider and model information
67+
promptUI.note(
68+
trimLines(`
69+
Profile: ${yellow(profile)}
70+
Provider: ${yellow(config.provider)}
71+
Model: ${yellow(config.model)}
72+
Endpoint: ${yellow(config.baseUrl)}
73+
`),
74+
);
75+
76+
await gitService.assertGitRepo();
77+
78+
if (stageAll) {
79+
const stagingSpinner = promptUI.spinner();
80+
stagingSpinner.start('Staging all files');
81+
await gitService.stageAllFiles();
82+
stagingSpinner.stop('All files staged');
83+
}
84+
85+
if (splitMode) {
86+
await handleSplitMode(aiCommitSplittingService, aiCommitMessageService, gitService, promptUI);
87+
} else {
88+
await handleAgentMode(aiAgentService, gitService, promptUI);
89+
}
90+
} catch (error) {
91+
if (isError(error)) {
92+
promptUI.outro(`${red('✖')} ${error.message}`);
93+
} else {
94+
promptUI.outro(`${red('✖')} An unknown error occurred`);
95+
}
96+
handleCliError(error);
97+
process.exit(1);
98+
}
99+
};
100+
101+
async function handleAgentMode(
102+
aiAgentService: AIAgentService,
103+
gitService: GitService,
104+
promptUI: ClackPromptService,
105+
): Promise<void> {
106+
const analyzeSpinner = promptUI.spinner();
107+
analyzeSpinner.start('AI agent is analyzing the repository...');
108+
109+
try {
110+
const result = await aiAgentService.generateCommitWithAgent({
111+
onToolCall: (message: string) => {
112+
analyzeSpinner.message(`AI agent: ${message}`);
113+
},
114+
});
115+
analyzeSpinner.stop('Repository analysis complete');
116+
117+
// Display the generated commit message
118+
promptUI.log.step('Generated commit message:');
119+
promptUI.log.message(green(result.commitMessage));
120+
121+
if (result.body) {
122+
promptUI.log.step('Commit body:');
123+
promptUI.log.message(result.body);
124+
}
125+
126+
if (!result.commitMessage) {
127+
throw new KnownError('No commit message was generated by the agent. Try again.');
128+
}
129+
130+
// Use the agent-specific review and revision flow
131+
const reviewResult = await agentStreamingReviewAndRevise({
132+
aiAgentService,
133+
promptUI,
134+
message: result.commitMessage,
135+
body: result.body || '',
136+
});
137+
138+
if (!reviewResult?.accepted) {
139+
return;
140+
}
141+
142+
const message = reviewResult.message ?? '';
143+
const body = reviewResult.body ?? '';
144+
145+
const fullMessage = `${message}\n\n${body}`.trim();
146+
await gitService.commitChanges(fullMessage);
147+
148+
promptUI.outro(`${green('✔')} Successfully committed with AI agent`);
149+
} catch (error) {
150+
analyzeSpinner.stop('Agent analysis failed');
151+
throw error;
152+
}
153+
}
154+
155+
const handleSplitMode = async (
156+
aiCommitSplittingService: AICommitSplittingService,
157+
aiCommitMessageService: AICommitMessageService,
158+
gitService: GitService,
159+
promptUI: ClackPromptService,
160+
): Promise<void> => {
161+
// Check if we have staged changes
162+
const hasStagedChanges = await gitService.hasStagedChanges();
163+
if (!hasStagedChanges) {
164+
throw new KnownError(
165+
trimLines(`
166+
No staged changes found. Stage your changes manually, or automatically stage all changes with the \`--stage-all\` flag.
167+
`),
168+
);
169+
}
170+
171+
const result = await handleCommitSplitting({
172+
aiCommitSplittingService,
173+
aiCommitMessageService,
174+
gitService,
175+
promptUI,
176+
});
177+
178+
if (result.accepted && result.commitCount && result.commitCount > 0) {
179+
promptUI.outro(
180+
`${green('✔')} Successfully created ${result.commitCount} commit${result.commitCount > 1 ? 's' : ''} using AI-guided splitting`,
181+
);
182+
} else {
183+
promptUI.outro(`${red('✖')} No commits were created`);
184+
}
185+
};
186+
6187
export const agentCommand = new Command('agent')
7188
.description('Enable AI agent mode for autonomous repository analysis')
8189
.addOption(new Option('--api-key <apiKey>', 'API key for the AI provider'))
@@ -16,8 +197,7 @@ export const agentCommand = new Command('agent')
16197
.addOption(new Option('--stage-all', 'Stage all modified files before generating commit'))
17198
.action(async (options) => {
18199
container.bind(CLI_ARGUMENTS).toConstantValue(options);
19-
await aiCommits({
20-
agentMode: true,
200+
await aiCommitsAgent({
21201
container,
22202
profile: options.profile,
23203
splitMode: options.split,

0 commit comments

Comments
 (0)