Files
orca/src/shared/commit-message-plan.test.ts
T
JinjingandOrca 006a4ef3a5 Customizable Source Control AI action recipes with per-action agent and command templates (#4868)
* WIP: Changes before auto-review fixes

* Customize Source Control AI action recipes

- Add per-action CLI arguments for generation and launch flows so saved
  recipes can select model flags without putting prompts in argv.
- Share the text-generation dialog between commit messages and hosted-review
  details, with first-run defaults and repo/global recipe support.
- Launch fix-check agents directly from saved recipes and harden prompt/template
  handling for invalid args, blank prompts, and inherited variables.

* Allow custom commands for source control action recipes

- Let text action recipes save and resolve the custom-command sentinel
- Add settings UI for custom command recipes and preserve per-action defaults
- Split large source-control dialogs and direct launch helpers into focused modules
- Keep launch actions from treating custom text agents as runnable TUI agents

* Add per-repo Source Control AI enablement, custom command, and save-targ

- Repo overrides now support `enabled` and `customAgentCommand`, letting
  repositories opt in/out of Source Control AI independently and supply a
  repo-scoped custom command that takes precedence over the global one.
- Recipe-save dialogs gained a save-target selector ("Don't save / Save for
  this repo / Save as global default") replacing the old boolean checkbox,
  routing saves through the new `saveSourceControlActionRecipe` helper in
  `source-control-ai-recipe-save.ts`.
- `normalizeRepoSourceControlAiOverrides` now returns `undefined` for empty
  objects and passes the `null` sentinel through the IPC/RPC layer so the
  persistence layer can clear repo overrides cleanly.
- `resolveSourceControlLaunchPlatform` resolves the correct shell platform
  for SSH and WSL worktrees so agent launch commands are built correctly.
- Settings UI gained `RepositorySourceControlAiEnablement` and
  `RepositorySourceControlAiCustomCommand` rows; draft/label logic was
  extracted into focused modules to stay within lint line limits.

* Extract action recipe defaults into own component and use id-prefixed wo

- Move action recipe draft state and UI out of CommitMessageAiPane into SourceControlAiActionRecipeDefaults and source-control-ai-action-recipe-draft.ts to respect the max-lines lint rule
- Use toRuntimeWorktreeSelector() across all runtime git RPC calls so the runtime can resolve worktrees by ID rather than path
- Fix SSH launch platform resolution to use the repo's connection when the newly created worktree isn't hydrated yet
- Add edit and delete handlers for PR conversation comments with confirmation dialog
- Use text-status-success design token instead of hardcoded text-emerald-500

* add more search keyword

* Rename "Enable Source Control AI defaults" to "Show Source Control AI ac

* fix test

* Remove unused imports and variable assignment in launch-work-item-direct

* Fix test mocks to use `mocks.store` instead of `storeState.value` for di

* Extract Source Control AI logic into focused modules with fix-checks dia

---------

Co-authored-by: Orca <help@stably.ai>
2026-06-08 14:08:35 -07:00

322 lines
7.0 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { planCommitMessageGeneration } from './commit-message-plan'
describe('planCommitMessageGeneration', () => {
it('plans Claude non-interactive generation with the prompt on stdin only', () => {
const result = planCommitMessageGeneration(
{
agentId: 'claude',
model: 'sonnet',
thinkingLevel: 'high'
},
'PROMPT'
)
expect(result).toEqual({
ok: true,
plan: {
binary: 'claude',
args: [
'-p',
'--output-format',
'text',
'--model',
'sonnet',
'--permission-mode',
'plan',
'--effort',
'high'
],
stdinPayload: 'PROMPT',
label: 'Claude'
}
})
})
it('plans OpenCode run with prompt on stdin and model variant', () => {
const result = planCommitMessageGeneration(
{
agentId: 'opencode',
model: 'opencode/gpt-5.4-mini',
thinkingLevel: 'high'
},
'PROMPT'
)
expect(result).toEqual({
ok: true,
plan: {
binary: 'opencode',
args: [
'run',
'--model',
'opencode/gpt-5.4-mini',
'--agent',
'build',
'--format',
'default',
'--variant',
'high'
],
stdinPayload: 'PROMPT',
label: 'OpenCode'
}
})
})
it('keeps OpenCode preset command overrides while sending the prompt on stdin', () => {
const result = planCommitMessageGeneration(
{
agentId: 'opencode',
model: 'opencode/gpt-5.4-mini',
agentCommandOverride: 'npx opencode'
},
'PROMPT'
)
expect(result).toEqual({
ok: true,
plan: {
binary: 'npx',
args: [
'opencode',
'run',
'--model',
'opencode/gpt-5.4-mini',
'--agent',
'build',
'--format',
'default'
],
stdinPayload: 'PROMPT',
label: 'OpenCode'
}
})
})
it('allows discovered dynamic models that are not in the seed catalog', () => {
const result = planCommitMessageGeneration(
{
agentId: 'cursor',
model: 'gpt-5.2',
thinkingLevel: 'xhigh'
},
'PROMPT'
)
expect(result).toEqual({
ok: true,
plan: {
binary: 'cursor-agent',
args: [
'--print',
'--mode',
'ask',
'--trust',
'--output-format',
'text',
'--model',
'gpt-5.2',
'PROMPT'
],
stdinPayload: null,
label: 'Cursor'
}
})
})
it('plans Codex exec as non-interactive read-only generation with the prompt on stdin only', () => {
const result = planCommitMessageGeneration(
{
agentId: 'codex',
model: 'gpt-5.4-mini',
thinkingLevel: 'medium'
},
'PROMPT'
)
expect(result).toEqual({
ok: true,
plan: {
binary: 'codex',
args: [
'exec',
'--ephemeral',
'--skip-git-repo-check',
'-s',
'read-only',
'--model',
'gpt-5.4-mini',
'-c',
'model_reasoning_effort=medium'
],
stdinPayload: 'PROMPT',
label: 'Codex'
}
})
})
it('uses preset agent command overrides as the spawn command prefix', () => {
const result = planCommitMessageGeneration(
{
agentId: 'codex',
model: 'gpt-5.4-mini',
agentCommandOverride: 'npx codex'
},
'PROMPT'
)
expect(result).toMatchObject({
ok: true,
plan: {
binary: 'npx',
args: [
'codex',
'exec',
'--ephemeral',
'--skip-git-repo-check',
'-s',
'read-only',
'--model',
'gpt-5.4-mini'
],
stdinPayload: 'PROMPT'
}
})
})
it('appends per-action CLI arguments after the built-in model args for stdin agents', () => {
const result = planCommitMessageGeneration(
{
agentId: 'codex',
model: 'gpt-5.4-mini',
agentArgs: '--model gpt-5.5 --sandbox read-only'
},
'PROMPT'
)
expect(result).toMatchObject({
ok: true,
plan: {
args: [
'exec',
'--ephemeral',
'--skip-git-repo-check',
'-s',
'read-only',
'--model',
'gpt-5.4-mini',
'--model',
'gpt-5.5',
'--sandbox',
'read-only'
],
stdinPayload: 'PROMPT'
}
})
})
it('appends per-action CLI arguments for stdin agents', () => {
const result = planCommitMessageGeneration(
{
agentId: 'opencode',
model: 'opencode/gpt-5.4-mini',
agentArgs: '--model opencode/gpt-5.5'
},
'PROMPT'
)
expect(result).toMatchObject({
ok: true,
plan: {
args: [
'run',
'--model',
'opencode/gpt-5.4-mini',
'--agent',
'build',
'--format',
'default',
'--model',
'opencode/gpt-5.5'
],
stdinPayload: 'PROMPT'
}
})
})
it('keeps custom per-action CLI arguments before a positional prompt', () => {
const result = planCommitMessageGeneration(
{
agentId: 'custom',
model: '',
customAgentCommand: 'agent --message {prompt}',
agentArgs: '--model gpt-5.5'
},
'PROMPT'
)
expect(result).toEqual({
ok: true,
plan: {
binary: 'agent',
args: ['--message', '--model', 'gpt-5.5', 'PROMPT'],
stdinPayload: null,
label: 'agent'
}
})
})
it('appends custom per-action CLI arguments when the prompt is sent on stdin', () => {
const result = planCommitMessageGeneration(
{
agentId: 'custom',
model: '',
customAgentCommand: 'agent --message',
agentArgs: '--model gpt-5.5'
},
'PROMPT'
)
expect(result).toMatchObject({
ok: true,
plan: {
args: ['--message', '--model', 'gpt-5.5'],
stdinPayload: 'PROMPT'
}
})
})
it('rejects invalid per-action CLI arguments before spawning', () => {
const result = planCommitMessageGeneration(
{
agentId: 'claude',
model: 'haiku',
agentArgs: '--model "unterminated'
},
'PROMPT'
)
expect(result).toEqual({
ok: false,
error: 'CLI arguments are invalid: Unclosed quote in command template.'
})
})
it('rejects invalid preset agent command overrides before spawning', () => {
const result = planCommitMessageGeneration(
{
agentId: 'claude',
model: 'haiku',
agentCommandOverride: 'claude "unterminated'
},
'PROMPT'
)
expect(result).toEqual({
ok: false,
error: 'Agent command override is invalid: Unclosed quote in command template.'
})
})
})