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
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,11 @@ keyboard alone, and the mouse stays first-class.
`.code-workspace`), native macOS
menubar, open in your editor or terminal, settings (⌘,) for appearance / diff /
git / integrations / AI, in-app updates.
- **AI commit messages** — suggest subject + body from staged changes via
your ChatGPT subscription (Codex CLI) or Claude Code CLI; Settings → AI
for sign-in, provider choice, and CLI health checks.
- **AI commit messages** — suggest subject + body from staged changes (or all
unstaged changes when nothing is staged) via
your ChatGPT subscription (Codex CLI, `gpt-5.6-luna`) or Claude Code CLI
(`claude-sonnet-5`); Settings → AI for sign-in, provider choice, and CLI
health checks.
- **Fast by design** — reads go through [gix](https://github.com/GitoxideLabs/gitoxide),
writes through git2 and your system `git`. Performance targets live in
[`PRD.md`](./PRD.md) §8 and are measured in
Expand Down
12 changes: 7 additions & 5 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -1661,15 +1661,17 @@ is preflighted before Strand reports that the browser or CLI flow has started.
`docs/strand-cli.md` + task breakdown in TASKS.md.
- Plugin / extension surface
- AI features (writing suggestions, conflict hints) — PRD Q3
- ☑ Commit message suggestions from staged diffs (Codex / Claude Code CLIs)
- ☑ Commit message suggestions from staged diffs, or all unstaged changes
when nothing is staged (Codex / Claude Code CLIs)
- ☑ Pull-request title/description suggestions from committed branch diffs
- ◐ Built-in PR review surface — moved into 1.0 with the GitHub/Azure read-only
foundation; GitLab/Bitbucket adapters and review/merge parity continue here.

**AI commit messages (2026-07-01):** Subscription-first suggestions from staged
diffs — Codex CLI (`codex login` / `codex exec`) for ChatGPT Plus, Claude Code
CLI (`claude -p`) for Anthropic. Rust `ai/` module + four IPC commands; Settings
→ AI for sign-in; CommitBar sparkle button; palette + ⌘⇧M. Verified:
**AI commit messages (2026-07-01):** Subscription-first suggestions prefer the
staged diff, and fall back to all unstaged changes when no staged diff exists —
Codex CLI (`codex login` / `codex exec`) for ChatGPT Plus, Claude Code CLI
(`claude -p`) for Anthropic. Rust `ai/` module + four IPC commands; Settings →
AI for sign-in; CommitBar sparkle button; palette + ⌘⇧M. Verified:
`cargo test -p strand-tauri`, `pnpm --filter ./ui exec tsc --noEmit`.

---
Expand Down
3 changes: 2 additions & 1 deletion TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1760,7 +1760,8 @@ extraction above as prerequisite. **Do not start before 1.0 ships**

- ☑ Rust `ai/` module + IPC (`ai_provider_*`, `repo_suggest_commit_message`)
- ☑ Settings → AI (ChatGPT / Claude Code sign-in, custom CLI paths)
- ☑ CommitBar Suggest + palette / ⌘⇧M shortcut
- ☑ CommitBar Suggest + palette / ⌘⇧M shortcut (prefers the staged diff; falls
back to all unstaged changes when no staged diff exists)
- ☑ Pull-request title/description suggestions from committed merge-base diffs
(`repo_suggest_pull_request`, Create PR **Fill with Codex/Claude Code**)
- ☑ Windows CLI spawning hardened (DAN-11: `ai/bin.rs` resolves `.exe`/`.cmd`/
Expand Down
4 changes: 4 additions & 0 deletions crates/strand-tauri/src/ai/claude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use super::bin::{resolve_claude, run_capture, spawn_detached, STATUS_TIMEOUT, SU
use super::AiProviderStatus;

const CLAUDE_INSTALL: &str = "https://code.claude.com/docs/en/setup";
/// A fast, focused model is sufficient for short commit and PR copy.
const SUGGEST_MODEL: &str = "claude-sonnet-5";

pub fn status(cli_override: Option<&str>) -> AiProviderStatus {
let Some(bin) = resolve_claude(cli_override) else {
Expand Down Expand Up @@ -88,6 +90,8 @@ pub fn suggest(
&bin,
&[
"-p",
"--model",
SUGGEST_MODEL,
"--output-format",
"json",
"--tools",
Expand Down
4 changes: 4 additions & 0 deletions crates/strand-tauri/src/ai/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use super::bin::{resolve_codex, run_capture, spawn_detached, STATUS_TIMEOUT, SUG
use super::AiProviderStatus;

const CODEX_INSTALL: &str = "https://developers.openai.com/codex";
/// A fast, focused model is sufficient for short commit and PR copy.
const SUGGEST_MODEL: &str = "gpt-5.6-luna";

pub fn status(cli_override: Option<&str>) -> AiProviderStatus {
let Some(bin) = resolve_codex(cli_override) else {
Expand Down Expand Up @@ -61,6 +63,8 @@ pub fn suggest(
&bin,
&[
"exec",
"--model",
SUGGEST_MODEL,
"--cd",
repo_path.to_str().ok_or("invalid repo path")?,
"--sandbox",
Expand Down
2 changes: 1 addition & 1 deletion crates/strand-tauri/src/ai/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ pub fn suggest_commit_message(
cli_override: Option<&str>,
) -> Result<CommitMessageSuggestion, String> {
if diffs.is_empty() {
return Err("Nothing stagedstage changes before generating a message.".into());
return Err("Nothing changedmake a change before generating a message.".into());
}
let text = format!(
"{}\n\nRemember: reply with JSON only: {{\"subject\":\"...\",\"body\":\"...\"}}",
Expand Down
6 changes: 3 additions & 3 deletions crates/strand-tauri/src/ai/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use strand_core::diff::{DiffStatus, FileDiff};
const MAX_FILES: usize = 8;
const MAX_TOTAL_CHARS: usize = 12_000;

const INSTRUCTION: &str = "Write a git commit message for the staged changes below.\n\
const INSTRUCTION: &str = "Write a git commit message for the changes below.\n\
Use conventional commit style when appropriate.\n\
Reply with JSON only, no markdown fences: {\"subject\":\"...\",\"body\":\"...\"}\n\
Keep subject at most 72 characters. Body may be empty string if not needed.";
Expand All @@ -14,10 +14,10 @@ Reply with JSON only, no markdown fences: {\"title\":\"...\",\"description\":\".
Keep the title concise. Write a useful Markdown description that explains what changed and why.\n\
Mention testing only when the changes provide clear evidence; do not invent results or implementation details.";

/// Build the user prompt sent to Codex / Claude from staged file diffs.
/// Build the user prompt sent to Codex / Claude from the selected file diffs.
pub fn build_prompt(diffs: &[FileDiff]) -> String {
let mut out = String::from(INSTRUCTION);
out.push_str("\n\n## Staged changes\n");
out.push_str("\n\n## Changes\n");
append_diffs(&mut out, diffs);
out
}
Expand Down
10 changes: 9 additions & 1 deletion crates/strand-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1300,7 +1300,15 @@ pub async fn repo_suggest_commit_message(
) -> CmdResult<ai::CommitMessageSuggestion> {
run_blocking("ai suggest", move || {
let repo = Repo::discover(&path)?;
let diffs = repo.diff_staged()?;
// A commit must use staged changes, but a suggestion is useful before
// the user has staged anything. Prefer the exact staged set whenever
// it exists; otherwise describe the whole working-tree delta.
let staged_diffs = repo.diff_staged()?;
let diffs = if staged_diffs.is_empty() {
repo.diff_unstaged()?
} else {
staged_diffs
};
let override_path = ai_cli_override(provider, openai_cli, anthropic_cli);
ai::suggest_commit_message(
provider,
Expand Down
16 changes: 9 additions & 7 deletions ui/src/views/LocalChanges.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ export function LocalChanges() {
</PanelGroup>
</div>

<CommitBar canCommit={staged.length > 0} />
<CommitBar canCommit={staged.length > 0} hasChanges={staged.length > 0 || unstaged.length > 0} />

{resolverFile && (
<MergeResolver path={resolverFile} onClose={() => setResolverFile(null)} />
Expand Down Expand Up @@ -1271,7 +1271,7 @@ function BlockActions({

// ─── Commit bar ─────────────────────────────────────────────────────────────

function CommitBar({ canCommit }: { canCommit: boolean }) {
function CommitBar({ canCommit, hasChanges }: { canCommit: boolean; hasChanges: boolean }) {
const activePath = useRepo((s) => s.activePath);
const commit = useRepo((s) => s.commit);
const suggestCommitSignal = useRepo((s) => s.suggestCommitSignal);
Expand Down Expand Up @@ -1311,7 +1311,7 @@ function CommitBar({ canCommit }: { canCommit: boolean }) {
}

const suggest = useCallback(async () => {
if (!activePath || !canCommit) return;
if (!activePath || !hasChanges) return;
setSuggesting(true);
setCommitError(null);
try {
Expand Down Expand Up @@ -1339,7 +1339,7 @@ function CommitBar({ canCommit }: { canCommit: boolean }) {
} finally {
setSuggesting(false);
}
}, [activePath, aiProvider, anthropicCli, canCommit, openaiCli]);
}, [activePath, aiProvider, anthropicCli, hasChanges, openaiCli]);

useEffect(() => {
if (!suggestCommitSignal) return;
Expand Down Expand Up @@ -1369,11 +1369,13 @@ function CommitBar({ canCommit }: { canCommit: boolean }) {
const disabled = submitting || !subject.trim() || (!canCommit && !amend);
// Missing CLI does not disable the button: clicking surfaces the backend's
// install/sign-in hint inline, instead of a dead control (DAN-11).
const suggestDisabled = suggesting || submitting || !canCommit;
const suggestDisabled = suggesting || submitting || !hasChanges;
const suggestTitle = suggesting
? 'Generating commit message…'
: !canCommit
? 'Stage changes to suggest a commit message'
: !hasChanges
? 'Make changes to suggest a commit message'
: !canCommit
? 'Suggest commit message from all unstaged changes'
: !aiInstalled
? 'Install the CLI (Settings → AI) to enable suggestions'
: 'Suggest commit message from staged changes';
Expand Down
2 changes: 1 addition & 1 deletion website/docs/everyday-git.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ The commit form takes a subject and an optional description body. `Mod+Enter` in

### AI commit message suggestions

The sparkle button next to the subject field (or `Mod+Shift+M`, or the palette action "Suggest commit message") generates a commit message from your **staged** changes. Suggestions use your own subscription CLIs — the Codex CLI (ChatGPT) or the Claude Code CLI — with no Strand-side API key. Pick the provider and sign in under Settings → AI; see [Settings](settings.md) for setup.
The sparkle button next to the subject field (or `Mod+Shift+M`, or the palette action "Suggest commit message") generates a commit message from your **staged** changes. If nothing is staged, it uses all unstaged changes instead, so it is available as soon as there is work to describe. Suggestions use your own subscription CLIs — the Codex CLI (ChatGPT) or the Claude Code CLI — with no Strand-side API key. Pick the provider and sign in under Settings → AI; see [Settings](settings.md) for setup.

## Fetch, pull, push

Expand Down
11 changes: 6 additions & 5 deletions website/docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,13 @@ Configure the external editor and terminal that Strand's "Open in editor" (`Mod+

## AI

Strand can suggest a commit message from staged changes or draft pull-request
text from committed branch changes. It has no API key of its own — suggestions
run through a CLI you already have, on your own subscription. Auth and billing
stay entirely in the vendor's CLI; Strand only orchestrates it.
Strand can suggest a commit message from staged changes, or all unstaged changes
when nothing is staged, and draft pull-request text from committed branch
changes. It has no API key of its own — suggestions run through a CLI you
already have, on your own subscription. Auth and billing stay entirely in the
vendor's CLI; Strand only orchestrates it.

- **AI writing provider** — "OpenAI (ChatGPT subscription)" (default), which uses your ChatGPT subscription via the Codex CLI, or "Anthropic (Claude Code CLI)", which uses the Claude Code CLI (`claude`).
- **AI writing provider** — "OpenAI (ChatGPT subscription)" (default), which uses your ChatGPT subscription via the Codex CLI (`gpt-5.6-luna`), or "Anthropic (Claude Code CLI)", which uses the Claude Code CLI (`claude-sonnet-5`). These fixed, focused models keep short commit-message and PR-draft requests responsive rather than inheriting your coding CLI's default model.
- **Codex CLI** — an optional custom path (leave empty to use `codex` on PATH), a status line, and **Sign in with ChatGPT** / **Sign out** buttons.
- **Claude Code CLI** — an optional custom path (leave empty to use `claude` on PATH), a status line, and **Sign in to Claude Code** / **Sign out** buttons.
- **Check CLI status** — checks both CLIs and reports whether each is missing,
Expand Down
Loading