Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ const baseProviderSchema = z.object({
// CLI-specific schema extends base
export const cliProviderSchema = baseProviderSchema.extend({
type: z.literal('cli'),
provider: z.enum(['claude', 'codex', 'cursor']),
provider: z.enum(['claude', 'codex', 'gemini']),
command: z.string().optional(),
args: z.array(z.string()).optional(),
});
Expand Down
8 changes: 5 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions docs/constitutions/v3/patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ npx commitment --message-only > "$1" # ❌ Overrides user messages!

```typescript
❌ V1 Pattern (removed):
const chain = [claude, codex, cursor]; // Fallback chain
const chain = [claude, codex, gemini]; // Fallback chain
for (const provider of chain) {
if (await provider.isAvailable()) {
return await provider.generate(prompt);
Expand Down Expand Up @@ -749,8 +749,8 @@ class CommitMessageGenerator {

```typescript
✅ Correct:
const SUPPORTED_PROVIDERS = ['claude', 'codex', 'cursor'] as const;
type Provider = typeof SUPPORTED_PROVIDERS[number]; // 'claude' | 'codex' | 'cursor'
const SUPPORTED_PROVIDERS = ['claude', 'codex', 'gemini'] as const;
type Provider = typeof SUPPORTED_PROVIDERS[number]; // 'claude' | 'codex' | 'gemini'

const config = {
timeout: 120000,
Expand Down
2 changes: 1 addition & 1 deletion docs/constitutions/v3/tech-stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ const result = match(provider)

**Detection:** Check for `claude` in PATH

### Codex CLI (from Cursor)
### Codex CLI

**Purpose:** Secondary AI provider (fallback)
**Status:** ✅ APPROVED
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
},
"dependencies": {
"chalk": "^5.6.2",
"commander": "^14.0.1",
"execa": "^9.6.0",
"sade": "^1.8.1",
"ts-pattern": "^5.8.0",
"zod": "3"
},
Expand Down Expand Up @@ -56,7 +56,7 @@
"scripts": {
"build": "bun run build.ts",
"check-types": "bun tsc --noEmit",
"clean": "rm -rf dist",
"clean": "rm -r dist node_modules",
"dev": "bun --watch build.ts",
"eval": "bun run src/eval/run-eval.ts",
"eval:claude": "bun run src/eval/run-eval.ts --agent claude",
Expand Down
1 change: 1 addition & 0 deletions src/agents/__tests__/codex.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ describe('CodexAgent', () => {
'codex',
[
'exec',
'--skip-git-repo-check',
'--output-last-message',
expect.stringMatching(/\/tmp\/codex-output-\d+\.txt/),
prompt,
Expand Down
13 changes: 9 additions & 4 deletions src/agents/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,15 @@ export class CodexAgent extends BaseAgent {

try {
// Execute Codex CLI in non-interactive mode
const result = await exec('codex', ['exec', '--output-last-message', tmpFile, prompt], {
cwd: workdir,
timeout: 120_000, // 2 minutes
});
// --skip-git-repo-check allows running in non-git directories (needed for eval system using /tmp)
const result = await exec(
'codex',
['exec', '--skip-git-repo-check', '--output-last-message', tmpFile, prompt],
{
cwd: workdir,
timeout: 120_000, // 2 minutes
}
);

// Read output from temp file or fallback to stdout
return await this._readOutput(tmpFile, result.stdout);
Expand Down
126 changes: 60 additions & 66 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import chalk from 'chalk';
import { program } from 'commander';
import sade from 'sade';
import { ZodError } from 'zod';

import { initCommand } from './cli/commands/init';
Expand Down Expand Up @@ -113,70 +113,64 @@ async function checkGitStatusOrExit(cwd: string): Promise<GitStatus> {
}

/**
* Main CLI function
* Main CLI setup
*/
async function main(): Promise<void> {
program
.name('commitment')
.description('AI-powered commit message generator using Claude or Codex')
.version(version);

// Init command - setup git hooks
program
.command('init')
.description('Initialize commitment hooks in your project')
.option('--hook-manager <type>', 'Hook manager to use: husky, simple-git-hooks, plain')
.option('--agent <name>', 'Default AI agent for hooks: claude, codex, gemini')
.option('--cwd <path>', 'Working directory', process.cwd())
.action(
async (options: {
cwd: string;
hookManager?: 'husky' | 'simple-git-hooks' | 'plain';
agent?: 'claude' | 'codex' | 'gemini';
}) => {
// Parse --agent manually from process.argv due to Commander.js subcommand option conflict
const agentFlagIndex = process.argv.indexOf('--agent');
const agentValue =
agentFlagIndex >= 0 && agentFlagIndex < process.argv.length - 1
? (process.argv[agentFlagIndex + 1] as 'claude' | 'codex' | 'gemini')
: undefined;

await initCommand({
agent: agentValue,
cwd: options.cwd,
hookManager: options.hookManager,
});
}
);

// Default command - generate commit message
program
.description(
'Generate commit message and create commit\n\n' +
'Available agents:\n' +
' claude - Claude CLI (default)\n' +
' codex - OpenAI Codex CLI\n' +
' gemini - Google Gemini CLI\n\n' +
'Example: commitment --agent claude --dry-run'
)
.option('--agent <name>', 'AI agent to use (claude, codex, gemini)', 'claude')
.option('--dry-run', 'Generate message without creating commit')
.option('--message-only', 'Output only the commit message (no commit)')
.option('--cwd <path>', 'Working directory', process.cwd())
.action(
async (options: {
agent?: string;
ai: boolean;
cwd: string;
dryRun?: boolean;
messageOnly?: boolean;
}) => {
await generateCommitCommand(options);
}
);

await program.parseAsync();
}
const prog = sade('commitment');

prog.version(version);

// Init command - setup git hooks
prog
.command('init')
.describe('Initialize commitment hooks in your project')
.option('--hook-manager', 'Hook manager to use: husky, simple-git-hooks, plain')
.option('--agent', 'Default AI agent for hooks: claude, codex, gemini')
.option('--cwd', 'Working directory', process.cwd())
.action(
async (options: {
cwd: string;
'hook-manager'?: 'husky' | 'simple-git-hooks' | 'plain';
agent?: 'claude' | 'codex' | 'gemini';
}) => {
await initCommand({
agent: options.agent,
cwd: options.cwd,
hookManager: options['hook-manager'],
});
}
);

// Default command - generate commit message
prog
.command('generate', '', { default: true })
.describe(
'Generate commit message and create commit\n\n' +
'Available agents:\n' +
' claude - Claude CLI (default)\n' +
' codex - OpenAI Codex CLI\n' +
' gemini - Google Gemini CLI\n\n' +
'Example: commitment --agent claude --dry-run'
)
.option('--agent', 'AI agent to use (claude, codex, gemini)', 'claude')
.option('--dry-run', 'Generate message without creating commit')
.option('--message-only', 'Output only the commit message (no commit)')
.option('--cwd', 'Working directory', process.cwd())
.action(
async (options: {
agent?: string;
ai: boolean;
cwd: string;
'dry-run'?: boolean;
'message-only'?: boolean;
}) => {
await generateCommitCommand({
agent: options.agent,
ai: options.ai,
cwd: options.cwd,
dryRun: options['dry-run'],
messageOnly: options['message-only'],
});
}
);

// Run CLI
await main();
prog.parse(process.argv);
2 changes: 1 addition & 1 deletion src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ Then run evaluation tests again.`,
*
* Includes installation instructions for the specific agent.
*
* @param name - Name of the agent (claude, codex, cursor)
* @param name - Name of the agent (claude, codex, gemini)
* @returns EvaluationError with installation instructions
*
* @example
Expand Down