diff --git a/CLAUDE.md b/CLAUDE.md index d313ecd..df16346 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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(), }); diff --git a/bun.lock b/bun.lock index 9d2564b..fb0be44 100644 --- a/bun.lock +++ b/bun.lock @@ -5,8 +5,8 @@ "name": "@arittr/commitment", "dependencies": { "chalk": "^5.6.2", - "commander": "^14.0.1", "execa": "^9.6.0", + "sade": "^1.8.1", "ts-pattern": "^5.8.0", "zod": "3", }, @@ -75,8 +75,6 @@ "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "commander": ["commander@14.0.1", "", {}, "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A=="], - "content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], @@ -181,6 +179,8 @@ "mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], + "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], @@ -221,6 +221,8 @@ "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], + "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], diff --git a/docs/constitutions/v3/patterns.md b/docs/constitutions/v3/patterns.md index 9494155..e14a5f6 100644 --- a/docs/constitutions/v3/patterns.md +++ b/docs/constitutions/v3/patterns.md @@ -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); @@ -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, diff --git a/docs/constitutions/v3/tech-stack.md b/docs/constitutions/v3/tech-stack.md index 28abe16..1832d8c 100644 --- a/docs/constitutions/v3/tech-stack.md +++ b/docs/constitutions/v3/tech-stack.md @@ -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 diff --git a/package.json b/package.json index d93d8a8..8aa5be5 100644 --- a/package.json +++ b/package.json @@ -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" }, @@ -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", diff --git a/src/agents/__tests__/codex.unit.test.ts b/src/agents/__tests__/codex.unit.test.ts index 32bdd74..4785eb6 100644 --- a/src/agents/__tests__/codex.unit.test.ts +++ b/src/agents/__tests__/codex.unit.test.ts @@ -73,6 +73,7 @@ describe('CodexAgent', () => { 'codex', [ 'exec', + '--skip-git-repo-check', '--output-last-message', expect.stringMatching(/\/tmp\/codex-output-\d+\.txt/), prompt, diff --git a/src/agents/codex.ts b/src/agents/codex.ts index 31a3e76..1cccb28 100644 --- a/src/agents/codex.ts +++ b/src/agents/codex.ts @@ -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); diff --git a/src/cli.ts b/src/cli.ts index a52d3c8..327af8a 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -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'; @@ -113,70 +113,64 @@ async function checkGitStatusOrExit(cwd: string): Promise { } /** - * Main CLI function + * Main CLI setup */ -async function main(): Promise { - 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 ', '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; - 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 ', '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; - 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); diff --git a/src/errors.ts b/src/errors.ts index 3154f56..75ccc18 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -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