VOOZH about

URL: https://deepwiki.com/guanguans/ai-commit/3.1-commit-command-workflow

⇱ Commit Command Workflow | guanguans/ai-commit | DeepWiki


Loading...
Menu

Commit Command Workflow

This page describes the end-to-end execution flow of the commit command: how it validates the git environment, collects a staged diff, routes to an AI generator, presents the result to the user, and finalizes the commit. It covers the internal structure of CommitCommand specifically.

  • For a reference of all CLI flags accepted by the command, see 3.2
  • For configuration management (config subcommand), see 3.3
  • For details on how individual AI generators work internally, see 5

Overview

The commit command is implemented in CommitCommand app/Commands/CommitCommand.php32-298

Its handle() method executes as a linear pipeline of tap() closures chained onto an empty Collection. Each closure either sets a $cachedDiff, $type, or $message variable by reference, or performs a side-effecting action (user prompt, git execution). If any step throws, execution stops.

End-to-end pipeline diagram:


Sources: app/Commands/CommitCommand.php52-105


Step-by-Step Breakdown

Step 1 — Git Repository Validation

git rev-parse --is-inside-work-tree

The first tap calls createProcess(['git', 'rev-parse', '--is-inside-work-tree'])->mustRun(). If the working directory is not inside a git repository, Symfony Process throws a ProcessFailedException and execution halts immediately.

The working directory is set from the path argument, which defaults to ConfigManager::localPath('') (the directory where the local config file would reside).

Sources: app/Commands/CommitCommand.php55-57 app/Commands/CommitCommand.php268-282


Step 2 — Collecting the Staged Diff

The second tap populates $cachedDiff using one of two sources:

SourceCondition
--diff option valueProvided by the caller directly
git diff --cached outputDefault — runs the diff command

The diff command is built by diffCommand() app/Commands/CommitCommand.php218-221 which appends any --diff-options (configured or passed via CLI) to ['git', 'diff', '--cached', ...].

By default, diff_options in config/ai-commit.php excludes lock files:

':!*-lock.json', ':!*.lock', ':!*.sum'

If $cachedDiff is empty after this step, a RuntimeException is thrown:

"There are no cached files to commit. Try running git add to cache some files."

Sources: app/Commands/CommitCommand.php58-64 config/ai-commit.php20-25


Step 3 — Commit Type Selection

The third tap calls $this->choice() to present an interactive prompt:

Please choice commit type

The choices come from configManager->get('types'), which is the types array in the config. The first entry (auto) is selected by default.

KeyDescription
autoAutomatically generate commit type
featA new feature
fixA bug fix
docsDocumentation only changes
styleFormatting changes
refactorCode change, not fix or feature
perfPerformance improvement
testAdding or correcting tests
buildBuild system changes
ciCI configuration changes
choreOther changes
revertReverts a previous commit

The selected $type string is passed into the prompt construction in step 4.

Sources: app/Commands/CommitCommand.php65-71 config/ai-commit.php50-63


Step 4 — AI Message Generation

The fourth tap builds the prompt and calls the generator:

Prompt construction — promptFor()

promptFor(string $cachedDiff, string $type) app/Commands/CommitCommand.php236-254 assembles the final prompt by substituting three placeholders inside the selected prompt template (resolved from prompts.<prompt-name>):

Placeholder config keyDefault valueReplaced with
type_mark<type>The selected type string
type_prompt_mark<type-prompt>A formatted type instruction, or empty string if auto
diff_mark<diff>The staged diff content

If the selected type is the first entry (i.e., auto), both type is reset to the raw <type> placeholder and typePrompt is cleared — leaving the AI free to choose the commit type.

When --verbose is passed, the final assembled prompt is printed via $this->output->note().

Generator dispatch

$this->generatorManager->driver($this->option('generator'))->generate($prompt)

GeneratorManager::driver() resolves the driver name (e.g., openai_chat) from the --generator option (or config default), instantiates the appropriate generator class, and calls generate(). The return value is passed through sanitizeMessage() (currently a no-op) to produce $message.

Sources: app/Commands/CommitCommand.php72-78 app/Commands/CommitCommand.php236-262 config/ai-commit.php39-48


Step 5 — Display and Confirmation Loop

The fifth tap displays the generated message and asks for confirmation:

  1. Calculates the maximum line length and prints a separator.
  2. Prints $message with $this->info().
  3. Calls $this->confirm('Do you want to commit this message?', true).

If the user answers no, $this->output->note('regenerating...') is printed and $this->handle() is called recursively — restarting the entire pipeline from the beginning (re-running git checks, re-generating, etc.).

If the user answers yes, execution falls through to step 6.

Sources: app/Commands/CommitCommand.php79-90


Step 6 — Git Commit Execution

The sixth tap is conditional on --dry-run:

Dry-run mode (--dry-run flag): The message is printed via $this->info($message) and the tap returns early. No git commit is run.

Normal mode: commitCommandFor(string $message) app/Commands/CommitCommand.php226-234 assembles the commit command:

git commit --message <message> [--no-edit] [--no-verify] [...commit_options]

Option assembly rules:

ConditionFlag added
shouldntEdit() returns true--no-edit appended
shouldntVerify() returns true--no-verify appended

shouldntEdit() app/Commands/CommitCommand.php284-287 returns true if:

  • TTY is not supported (Process::isTtySupported() is false), or
  • --no-edit flag was passed, or
  • no_edit is true in config.

When editing is supported, the process runs with setTty(true) so the user can edit the message in $EDITOR. The process timeout is removed (setTimeout(null)) to accommodate interactive editing.

Sources: app/Commands/CommitCommand.php91-104 app/Commands/CommitCommand.php226-234 app/Commands/CommitCommand.php284-297


Step 7 — Success Output

The final tap calls $this->output->success('Successfully generated and committed message.').

Sources: app/Commands/CommitCommand.php102-104


Key Internal Methods

Code entity map:


Sources: app/Commands/CommitCommand.php32-298


Initialization and Configuration Loading

Before handle() runs, Symfony Console calls initialize() app/Commands/CommitCommand.php198-216

If --config <file> is provided:

  1. configManager->replaceFrom($configFile) merges that file's values over the current config.
  2. The keys commit_options, diff_options, generator, and prompt are re-read and applied to the input options, overriding any CLI defaults.

This allows per-project config files to silently override generator selection, prompt templates, and diff/commit options.

Sources: app/Commands/CommitCommand.php198-216


Option Registration

Options are defined in configure() app/Commands/CommitCommand.php132-190 rather than in $signature (which is just 'commit'). This gives fine-grained control over InputOption types and defaults.

OptionShortTypeDefault from config
pathArgument (optional)ConfigManager::localPath('')
--commit-optionsArraycommit_options
--diff-optionsArraydiff_options
--generator-gRequired valuegenerator
--prompt-pRequired valueprompt
--no-editNone (flag)
--no-verifyNone (flag)
--config-cOptional value
--dry-runNone (flag)
--diffOptional value

Tab completion for --generator and --prompt is provided by complete() app/Commands/CommitCommand.php113-124 which reads generators and prompts keys from the config.

Sources: app/Commands/CommitCommand.php132-190 app/Commands/CommitCommand.php113-124


Full Workflow with Code References


Sources: app/Commands/CommitCommand.php52-105 app/Commands/CommitCommand.php198-216