Feature gaps identified by comparing gitAICommit against crabclaw's commit,
PR, branch, and provider orchestration. Each section now points at the exact
crabclaw file/function we can port or adapt, plus the current
gitAICommit code that would receive the change.
Gap: gitAICommit generates commit messages only. No PR support exists.
gitAICommit touch points:
src/main.rs:104-155— current generate/confirm/commit flowsrc/formatting/prompt.rs:21-150— commit-only prompt builder/template
crabclaw sources to port:
rust/crates/claw-cli/src/main.rs:1708-1738—run_pr()- builds a PR prompt from context + diff summary
- sanitizes output
- parses title/body
- shells out to
gh pr create --title ... --body-file ...
rust/crates/claw-cli/src/main.rs:2314-2322—parse_titled_body()rust/crates/claw-cli/src/main.rs:2301-2308—truncate_for_prompt()rust/crates/claw-cli/src/main.rs:2310-2312—sanitize_generated_message()
Adoption plan:
- Add
--pror aprsubcommand tosrc/cli/args.rs - Add a PR prompt mode to
PromptBuilder - Port
parse_titled_body()nearly verbatim - Reuse
truncate_for_prompt()for PR prompt payload limits - Write PR body to a temp file and call
gh pr create - Fallback to printing a draft when
ghis missing or fails - Completed TDD slice:
--pr --dry-runnow generates a real PR draft through the existing provider pathtests/interactive_test.rs— addedtest_pr_dry_run_generates_pr_title_and_body_from_repo_contexttests/cli_args_test.rs— addedtest_pr_flagsrc/cli/args.rs— added the--prflagsrc/formatting/prompt.rs— addedPromptBuilder::build_pr()and acrabclaw-styleTITLE:/BODY:PR prompt contract built from staged diff summary, recent commit context, and optional user contextsrc/pr.rs— addedPullRequestDraft::parse_or_normalize()so weak local-model output is normalized back into theTITLE:/BODY:shape instead of inventing a second CLI formattersrc/main.rs— routes--prthrough the existing provider-neutralLlmManagerand prints a PR draft in dry-run mode instead of entering the commit path- Leveraged
crabclaw'srun_pr()prompt contract from/home/npiesco/crabclaw/rust/crates/claw-cli/src/main.rs:2656-2665 - Leveraged
crabclaw's title/body parsing boundary from/home/npiesco/crabclaw/rust/crates/claw-cli/src/main.rs:2314-2322 - Reused the same truncation philosophy from
/home/npiesco/crabclaw/rust/crates/claw-cli/src/main.rs:2301-2308throughgitAICommit's existingtruncate_for_prompt()path instead of inventing another limiter
- Completed TDD slice: non-dry-run
--prnow attempts realgh pr createand falls back to the generated draft when creation failstests/interactive_test.rs— addedtest_pr_generation_falls_back_to_draft_when_gh_create_failssrc/pr.rs— addedcreate_pull_request_via_gh()which writes the generated PR body to a real temp file, calls the realgh pr create --title ... --body-file ...path, and disables interactive prompting viaGH_PROMPT_DISABLED=1andGIT_TERMINAL_PROMPT=0src/pr.rs— tightenedPullRequestDraftcleanup so repeatedTitle:/Body:echoes from weaker local models are stripped at the same parsing boundary instead of leaking into the final draftsrc/main.rs— now attempts real PR creation for--proutside dry-run and falls back to printing the normalized draft, plus an informationalgh pr create failed: ...message, when the realghcommand fails in the repository- Leveraged
crabclaw's realgh pr create --body-fileflow from/home/npiesco/crabclaw/rust/crates/claw-cli/src/main.rs:2667-2684instead of inventing a new executor
- Completed TDD slice: PR generation now threads the detected default branch into the real PR path
tests/interactive_test.rs— addedtest_pr_fallback_surfaces_detected_default_base_branch, which creates a real bareorigin, pushesmain, clones it, switches to a feature branch, runs the actual binary, and verifies the fallback PR output includesBASE: mainsrc/main.rs— detects the default branch before PR creation and surfaces it in dry-run, success, and fallback PR outputsrc/pr.rs—create_pull_request_via_gh()now passes--base <default_branch>to the realgh pr createinvocation instead of relying on GitHub CLI defaults- Leveraged
crabclaw's default-branch detection source from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1189-1203 - Leveraged
crabclaw's helper shape from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1246-1270 - Reused
crabclaw's real PR creation boundary from/home/npiesco/crabclaw/rust/crates/claw-cli/src/main.rs:2667-2684, adapting it to explicitly target the detected base branch
- Completed hardening slice discovered during regression: local OpenAI-compatible responses that return an empty body now fall back to the native Ollama path
src/llm/mod.rs—OpenAiCompatibleManager::generate_commit()now routes empty local OpenAI-compatible responses throughgenerate_via_ollama_native()instead of failing withOpenAI-compatible provider returned an empty response- This keeps the provider boundary aligned with the existing local-Ollama transport handling instead of weakening the real integration tests
- Next TDD target:
- complete the happy-path PR creation UX by surfacing the created PR URL cleanly, then move on to combined commit+push+PR orchestration
Gap: gitAICommit stops after git commit.
gitAICommit touch points:
src/main.rs:126-183— commit confirmation +perform_commit()src/git/collector.rs:232-245— current branch lookup
crabclaw sources to port:
rust/crates/commands/src/lib.rs:683-688—CommitPushPrRequestrust/crates/commands/src/lib.rs:810-915—handle_commit_push_pr_slash_command()rust/crates/commands/src/lib.rs:917-935—detect_default_branch()rust/crates/commands/src/lib.rs:1016-1050—build_branch_name()+slugify()rust/crates/commands/src/lib.rs:1052-1065— PR URL parsing helpersrust/crates/commands/src/lib.rs:1006-1013—write_temp_text_file()
Adoption plan:
- Add composable
--push,--pr, and--push-prflags - Extract post-generation workflow out of
src/main.rs - Port the commit/push/PR orchestration flow from
handle_commit_push_pr_slash_command() - Reuse the branch diff check against
<default>...HEAD - Reuse
gh pr view --json urlfallback when PR already exists - Completed TDD slice:
--pushnow commits and pushes the current feature branch to a real localorigintests/interactive_test.rs— addedtest_push_flag_pushes_committed_change_to_real_remote_feature_branch, which creates a real bare remote, clones it, switches to a feature branch, runs the actual binary, and verifies the pushed remote branch HEAD matches local HEADtests/cli_args_test.rs— addedtest_push_flagand extended combined-flag coveragesrc/cli/args.rs— added the--pushflagsrc/git/repository.rs— addedpush_current_branch()and promotedcurrent_branch()into the reusable git helper layer instead of reimplementing branch lookup insidemain.rssrc/git/mod.rs— exportspush_current_branch()andcurrent_branch()src/main.rs— after a successful real commit,--pushnow calls the shared git helper and surfaces a deterministic[PUSH]result block- Leveraged
crabclaw's current-branch and push orchestration direction from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1089-1125 - Leveraged
crabclaw's helper layer shape from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1207-1232instead of adding ad hoc git calls in the CLI flow
- Completed TDD slice:
--pushnow auto-creates and switches to a slugified feature branch when invoked from the default branchtests/interactive_test.rs— addedtest_push_flag_from_default_branch_creates_and_pushes_slugified_feature_branch, which starts onmain, runs the actual binary with--push, and verifies the flow switched to and pushed a real slugified branch derived from user contextsrc/git/repository.rs— addedensure_push_branch()plus direct ports ofbuild_branch_name()andslugify()into the reusable git helper layersrc/git/mod.rs— exportsensure_push_branch()src/main.rs— before pushing, the--pushpath now ensures it is off the detected default branch, using user context first and sanitized commit message second as the branch-name hint- Leveraged
crabclaw's default-branch push safety flow from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1089-1101 - Leveraged
crabclaw's branch naming helpers from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1288-1345instead of inventing a different slug strategy
- Completed TDD slice:
--push-prnow runs the real combined commit, push, and PR flow end to endtests/interactive_test.rs— addedtest_push_pr_from_default_branch_commits_pushes_and_falls_back_to_pr_draft, which creates a real bare remote, starts onmain, runs the actual binary with--push-pr, verifies a real commit was created, verifies the flow switched to and pushed a real slugified branch, and verifies PR creation falls back to a generatedTITLE:/BODY:draft whenghcannot create the PRtests/cli_args_test.rs— addedtest_push_pr_flagand extended default/combined flag coverage to include--push-prsrc/cli/args.rs— added the--push-prflag as a first-class combined orchestration pathsrc/main.rs— added the real combined flow: generate a commit message, create the commit, reuseensure_push_branch()andpush_current_branch(), then generate a PR draft and route it through the existinggh pr create/ fallback pathsrc/main.rs— addedgenerate_sanitized_commit_message()with a logged retry when a weak local-model response sanitizes to empty, fixing the real regression surfaced intest_push_flag_pushes_committed_change_to_real_remote_feature_branchduring full-suite validation- Leveraged
crabclaw'shandle_commit_push_pr_slash_command()orchestration flow from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1089-1157instead of inventing a second commit/push/PR pipeline - Reused the already-ported branch safety helpers derived from
/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1288-1345
- Completed TDD slice:
--push-prnow works when the worktree is clean but the current feature branch is already ahead of the default branchtests/interactive_test.rs— addedtest_push_pr_with_clean_worktree_uses_existing_branch_commits, which creates a real bare remote, pushesmain, creates and commits on a real feature branch, runs the actual binary with--push-prfrom a clean worktree, and verifies the flow does not exit early as “no changes”, pushes the existing branch, and falls back to a generated PR draftsrc/git/repository.rs— addedbranch_diff_stat(), which computes a realgit diff --stat <default>...HEADsummary for branch-ahead PR generationsrc/git/mod.rs— exportsbranch_diff_stat()src/formatting/prompt.rs— addedbuild_pr_from_branch_diff(), which reuses the existing PR prompt contract and truncation path but feeds it branch diff context instead of staged-worktree contextsrc/main.rs— before the old empty-worktree early return, the--push-prpath now detects default branch and checks whether the current branch is already ahead; if it is, the flow skips commit generation, reusespush_current_branch(), and generates the PR draft from branch diff context instead of bailing outsrc/main.rs— addedgenerate_pull_request_draft()with a logged retry when a weak local-model response cannot be parsed intoTITLE:/BODY:, fixing the real regression surfaced intest_pr_fallback_surfaces_detected_default_base_branchduring full-suite validation- Leveraged
crabclaw's combined orchestration direction from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1089-1157instead of inventing a separate clean-worktree PR path - Leveraged
crabclaw's branch comparison shape from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1117-1126by checking branch state against<default>...HEADrather than relying only on staged worktree status
- Completed TDD slice: plain
--prnow works when the worktree is clean but the current feature branch is already ahead of the default branchtests/interactive_test.rs— addedtest_pr_dry_run_with_clean_worktree_uses_existing_branch_commits, which creates a real bare remote, pushesmain, creates and commits on a real feature branch, runs the actual binary with--pr --dry-runfrom a clean worktree, and verifies the flow generates a PR draft againstmaininstead of exiting as “No changes detected in the repository.”src/main.rs— the branch-diff PR context gate now applies to plain--pras well as--push-pr, so clean branches that are ahead of default reuse the samebranch_diff_stat()path instead of falling through to the generic empty-worktree exitsrc/main.rs— the plain--prexecution path now reusesbuild_pr_from_branch_diff()when branch diff context is available, keeping the prompt contract identical to the already-ported clean-worktree--push-prpath instead of inventing a second PR-only prompt- Ported the same
crabclawbranch comparison behavior from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1117-1126that already drives the clean-worktree--push-prpath, extending it to plain PR generation instead of treating--pras staged-worktree-only
- Completed TDD slice:
--push-prnow reports the combined-flow skip reason when the worktree is clean and the current branch is not ahead of the default branchtests/interactive_test.rs— addedtest_push_pr_with_no_branch_changes_reports_combined_skip_reason, which creates a real bare remote, clonesmain, switches to a real feature branch with no commits ahead, runs the actual binary with--push-pr, and verifies the flow surfaces the combined skip reason instead of the generic empty-repository messagesrc/main.rs— when--push-prsees no staged/worktree changes andbranch_diff_stat()also shows no<default>...HEADchanges, it now prints[INFO] No branch changes to push or open as a pull request.and exits cleanly instead of falling through to the generic “No changes detected in the repository” path- Leveraged
crabclaw'shandle_commit_push_pr_slash_command()skip behavior from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1143-1149instead of inventing another empty-repository branch in the CLI flow
- Completed TDD slice:
--push-pr --dry-runnow previews the slugified feature branch it would use without mutating the repotests/interactive_test.rs— addedtest_push_pr_dry_run_previews_slugified_branch_without_switching, which creates a real bare remote, runs the actual binary frommainwith staged changes and--push-pr --dry-run, verifies the repo stays onmain, and verifies the output previews the slugified feature branch name instead of incorrectly showingBRANCH: mainsrc/git/repository.rs— addedpreview_push_branch(), which reuses the same default-branch and branch-name logic asensure_push_branch()but does not switch branchessrc/git/mod.rs— exportspreview_push_branch()src/main.rs— in the combined dry-run path, after commit-message generation, the CLI now previews the branch it would push by callingpreview_push_branch()with the same context-first / commit-message-second hint that the real push flow already uses- Leveraged
crabclaw's shared branch naming flow from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1121-1129and/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1314-1345instead of inventing a separate dry-run branch naming rule
- Completed TDD slice:
--push-pr --dry-runnow surfaces the push preview block and branch-action decision explicitlytests/interactive_test.rs— extendedtest_push_pr_dry_run_previews_slugified_branch_without_switchingso the real CLI must print[DRY RUN] Push preview:andBRANCH ACTION: switchedwhen running frommain, while still leaving the repository onmainsrc/main.rs— in the combined dry-run path, afterpreview_push_branch()resolves the non-mutating target branch, the CLI now prints the same push-preview structure as the real push flow, includingBRANCH ACTION: ready|switched,BRANCH: ..., andREMOTE: origin- Ported
crabclaw's branch-action output shape from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1206and continued reusing the shared branch naming flow from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1121-1129and/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1314-1345instead of inventing a separate combined dry-run preview contract
- Completed TDD slice:
--push --dry-runnow previews the slugified feature branch it would use without mutating the repotests/interactive_test.rs— addedtest_push_dry_run_previews_slugified_branch_without_switching, which creates a real bare remote, runs the actual binary frommainwith staged changes and--push --dry-run, verifies the repo stays onmain, and verifies the output previews the slugified feature branch name instead of behaving like a plain commit dry-runsrc/main.rs— in the plain push dry-run path, the CLI now emits a[DRY RUN] Push preview:block usingpreview_push_branch()before printing the generated commit message, so the preview matches the real push flow without switching branches- Reused the same
preview_push_branch()helper and therefore the samecrabclaw-derived branch naming flow from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1121-1129and/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1314-1345instead of inventing a separate dry-run rule for--push
- Completed TDD slice:
--push --dry-runnow previews whether the real flow would stay on the current branch or switch to a slugified feature branchtests/interactive_test.rs— extendedtest_push_dry_run_previews_slugified_branch_without_switchingto verify the real CLI output includesBRANCH ACTION: switchedwhen running frommain, while still leaving the repository onmainsrc/main.rs— the plain push dry-run preview now comparescurrent_branch()topreview_push_branch()and printsBRANCH ACTION: readyorBRANCH ACTION: switched, so the dry-run preview surfaces the same branch transition decision as the real push path- Leveraged
crabclaw's branch-action output shape from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1206while continuing to reuse the shared branch naming flow from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1121-1129and/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1314-1345
- Completed TDD slice: real GitHub-backed
--push-prnow creates a PR on the first run and surfaces the existing PR on the second runtests/interactive_test.rs— addedtest_push_pr_with_real_github_repo_creates_then_reuses_pull_request, which creates a disposable private GitHub repo withgh, clones it into/tmp, stages a real change, runs the actual binary with--push-pr, verifies a real PR is created, then runs the actual binary with--prfrom the clean feature branch and verifies the existing PR URL is surfaced on the second runtests/interactive_test.rs— addedDisposableGithubRepo,github_login(), and the live-URL parsing helpers needed to create and tear down the disposable private repo without introducing stubs or mock GitHub behaviorsrc/pr.rs— changedcreate_pull_request_via_gh()to returnPullRequestCreateResult::{Created, Existing}and ported thegh pr create->gh pr view --json urlfallback shape so the app can distinguish a newly created PR from an already-open onesrc/main.rs— both the plain--prand combined--push-prpaths now surface[PULL REQUEST] Created pull request:and[PULL REQUEST] Existing pull request:explicitly, including the real PR URL whenghreturns onesrc/main.rs— fixed the orchestration order the live GitHub test exposed: when--pushor--push-prstarts on the default branch,ensure_push_branch()now runs beforecommit_message_to_repo()so the real commit lands on the feature branch instead of onmain- Ported
crabclaw's existing-PR fallback behavior from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1210-1243, specifically thegh pr createfailure path that immediately triesgh pr view --json url - Ported
crabclaw's branch-first combined-flow ordering from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1150-1188, so branch creation/switching happens before the commit is written when the flow starts on the default branch
- Next TDD target:
- decide whether the live GitHub-backed test should stay
#[ignore]by default or be promoted into a dedicated opt-in CI job, now that the full disposable-repo lifecycle is proven end to end
- decide whether the live GitHub-backed test should stay
Gap: gitAICommit is Ollama-only.
gitAICommit touch points:
src/main.rs:58-65— hardwiresOllamaManagersrc/cli/args.rs:105-123— model selection assumes Ollamasrc/ollama/mod.rs:14-35—OllamaClientTraitsrc/ollama/client.rs— Ollama-only HTTP clientsrc/ollama/manager.rs— Ollama lifecycle management
crabclaw sources to port:
rust/crates/api/src/providers/mod.rs:12-24—Providertraitrust/crates/api/src/providers/mod.rs:26-32—ProviderKindrust/crates/api/src/providers/mod.rs:144-209— model aliasing + provider detectionrust/crates/api/src/client.rs:21-90—ProviderClientdispatch layerrust/crates/api/src/providers/openai_compat.rs:26-117—OpenAiCompatConfig+OpenAiCompatClient::{new,from_env,for_ollama}rust/crates/claw-cli/src/main.rs:1617-1622— provider-agnostic call site viarun_internal_prompt_text()
Adoption plan:
- Replace
OllamaClientTraitwith a provider-neutral trait/interface - Add a small
ProviderKindenum andProviderClient-style dispatch layer - Keep Ollama as one backend
- Port the OpenAI-compatible client shape for OpenAI and other
/v1/chat/completionsproviders - Move provider/base URL/API key selection into config + env resolution
- Completed TDD slice: route the real CLI through a provider-neutral manager while preserving Ollama as the default backend
tests/list_models_test.rs— addedtest_list_models_with_explicit_ollama_provider_flagsrc/main.rs— replaced the top-levelOllamaManagerwiring withllm::LlmManager, so--list-models, model checks, startup, and generation now go through one provider-neutral entrypointsrc/llm/mod.rs— addedProviderKind,LlmManagerOptions, andLlmManagerwith realOllamaandOpenAiCompatiblebackendssrc/llm/mod.rs— added an OpenAI-compatible/v1/modelsand/v1/chat/completionsclient shape so the abstraction is real, not a placeholdersrc/cli/args.rs— added--provider,--base-url, and--api-keysrc/config.rs— added persistedprovider,base_url, andapi_keyfields so provider selection is not CLI-onlytests/cli_args_test.rsandtests/config_test.rs— extended coverage for provider/base URL/API key parsing and config loading- Leveraged
gitsumintelligence's provider-neutral config and enum shape from/home/npiesco/gitsumintelligence/src/llm/provider.rsinstead of inventing another provider descriptor - Leveraged
ailighter's factory and OpenAI-compatible client pattern from /home/npiesco/ailighter/src-tauri/src/llm/factory.rs and /home/npiesco/ailighter/src-tauri/src/llm/providers/generic_openai.rs instead of inventing another HTTP shape
- Completed TDD slice:
openai-compatiblenow honors the local provider port when no explicit base URL is suppliedtests/list_models_test.rs— addedtest_list_models_with_openai_compatible_provider_uses_local_ollama_porttests/list_models_test.rs— boots a real local Ollama process throughOllamaManager::ensure_running()and then exercises the realgit-ai-commitbinary with--provider openai-compatible --port 11434 --list-modelssrc/llm/mod.rs— changedLlmManager::new()so theopenai-compatiblebackend defaults tohttp://localhost:{port}instead ofhttps://api.openai.comwhen--base-urlis omittedsrc/llm/mod.rs— keeps explicit--base-urloverride behavior intact while making the local-provider path usable for Ollama's/v1/modelsand/v1/chat/completionsendpoints- Leveraged
ailighter's local OpenAI-format provider pattern from /home/npiesco/ailighter/src-tauri/src/llm/factory.rs and /home/npiesco/ailighter/src-tauri/src/llm/providers/generic_openai.rs, specifically the idea that a local OpenAI-compatible backend should be addressable from local connection settings rather than hardcoded cloud defaults
- Completed TDD slice: non-Ollama providers no longer inherit Ollama default-model discovery
tests/cli_args_test.rs— addedtest_openai_compatible_provider_requires_explicit_modelsrc/cli/args.rs—Args::normalize_provider_defaults()now clears the implicit Ollama-derived model when--provider openai-compatibleis selected without an explicit--modelsrc/main.rs— added a provider-aware runtime guard so non-Ollama providers fail clearly if--modelis omitted- Leveraged
gitsumintelligence's provider-aware config semantics from/home/npiesco/gitsumintelligence/src/llm/provider.rsandcrabclaw's provider dispatch direction fromrust/crates/api/src/providers/mod.rs:144-209instead of keeping Ollama-specific defaulting logic at the top level
- Completed TDD slice: end-to-end
openai-compatiblegeneration now works against the real local Ollama pathtests/interactive_test.rs— addedtest_openai_compatible_provider_generates_commit_message_via_local_ollamasrc/llm/mod.rs—OpenAiCompatibleManager::generate_commit()now tries/v1/chat/completionsfirst, then falls back to native Ollama endpoints on local404responsessrc/llm/mod.rs— native fallback now supports both/api/chatand/api/generate, reusing the message-oriented Ollama shape instead of assuming one endpointsrc/ollama/client.rs— tightenedOllamaClient::is_running()so only successful/api/tagsresponses count as a healthy Ollama server;404no longer fools the provider tests into skipping real startuptests/interactive_test.rsandtests/list_models_test.rs— real Ollama-backed integration tests now run on a serializedserial(ollama)lane and keep the bootedOllamaManageralive for the duration of the test instead of dropping it immediately- Leveraged
crabclaw's OpenAI-compatible client boundary fromrust/crates/api/src/providers/openai_compat.rs:26-117,gitsumintelligence's native Ollama chat request shape from/home/npiesco/gitsumintelligence/src/llm/providers/ollama.rs, andailighter's native Ollama generate path from/home/npiesco/ailighter/src-tauri/src/llm/providers/ollama.rsrather than inventing another local-provider compatibility layer
- Completed TDD slice: provider-specific empty-list guidance for
--list-modelsnow lives behind the provider-neutral managertests/list_models_test.rs— addedtest_list_models_with_openai_compatible_provider_does_not_print_ollama_pull_hintsrc/llm/mod.rs— addedModelListingsoLlmManager::list_models()returns both model names and provider-aware empty-state guidance instead of a bareVec<String>src/llm/mod.rs—Ollamakeeps the existingollama pull <model>hint, whileOpenAiCompatiblenow returns the neutral messageNo models found for provider 'openai-compatible'.src/main.rs— the CLI now renders provider-aware empty-state text fromLlmManagerinstead of hardcoding an Ollama-only hint at the top level- Leveraged
crabclaw's provider-dispatch shape fromrust/crates/api/src/client.rs:21-90andrust/crates/api/src/providers/mod.rs:144-209by pushing provider-specific behavior behind the provider-neutral boundary instead of adding anotherif provider == ...branch inmain.rs
- Completed TDD slice: provider-specific model readiness now lives behind the provider-neutral manager
tests/interactive_test.rs— addedtest_openai_compatible_provider_fails_early_when_model_is_missingsrc/llm/mod.rs—LlmManager::ensure_model_available()now delegates readiness checks foropenai-compatibleinstead of silently no-oping outside Ollamasrc/llm/mod.rs—OpenAiCompatibleManager::ensure_model_available()now verifies the selected model against/v1/modelsbefore generation and fails withModel '<name>' is not available for provider 'openai-compatible'src/llm/mod.rs— extractedfetch_model_ids()so list-models and readiness checks share the same provider-native discovery path instead of duplicating endpoint logic- Leveraged
crabclaw's provider-neutral dispatch boundary fromrust/crates/api/src/client.rs:21-90andrust/crates/api/src/providers/mod.rs:144-209by moving readiness into the provider layer, not by adding another startup special-case inmain.rs
- Completed TDD slice: provider-specific startup messaging now lives behind the provider-neutral manager
tests/interactive_test.rs— addedtest_openai_compatible_provider_does_not_print_ollama_startup_bannersrc/llm/mod.rs— addedStartupStatusandLlmManager::startup_status()so backend-specific startup messaging is emitted from the provider layer instead of hardcoded at the CLI boundarysrc/main.rs— now prints startup messaging only when the selected provider returns one, which keeps the Ollama banner for the Ollama backend and suppresses it foropenai-compatible- Leveraged
crabclaw's provider-neutral dispatch boundary fromrust/crates/api/src/client.rs:21-90andrust/crates/api/src/providers/mod.rs:144-209by moving startup behavior behind the provider layer rather than adding anotherif provider == ...branch inmain.rs
- Completed TDD slice: provider-specific model-check messaging now lives behind the provider-neutral manager
tests/interactive_test.rs— addedtest_openai_compatible_provider_does_not_print_ollama_model_check_bannersrc/llm/mod.rs— addedReadinessStatusandLlmManager::readiness_status()so backend-specific readiness messaging is emitted from the provider layer instead of hardcoded at the CLI boundarysrc/main.rs— now prints the[CHECK]banner only when the selected provider returns one, which keeps the existing Ollama wording for Ollama and suppresses it foropenai-compatible- Leveraged
crabclaw's provider-neutral dispatch boundary fromrust/crates/api/src/client.rs:21-90andrust/crates/api/src/providers/mod.rs:144-209by moving readiness wording behind the provider layer rather than adding anotherif provider == ...branch inmain.rs
- Completed TDD slice: provider-specific analysis messaging now lives behind the provider-neutral manager
tests/interactive_test.rs— addedtest_openai_compatible_provider_does_not_print_analysis_bannersrc/llm/mod.rs— addedAnalysisStatusandLlmManager::analysis_status()so backend-specific analysis messaging is emitted from the provider layer instead of hardcoded at the CLI boundarysrc/main.rs— now prints the[ANALYZE]banner only when the selected provider returns one, which keeps the existing Ollama wording for Ollama and suppresses it foropenai-compatible- Leveraged
crabclaw's provider-neutral dispatch boundary fromrust/crates/api/src/client.rs:21-90andrust/crates/api/src/providers/mod.rs:144-209by moving analysis wording behind the provider layer rather than adding anotherif provider == ...branch inmain.rs
- Next TDD target:
- move the remaining provider-shaped lifecycle wording behind the provider-neutral layer so non-Ollama providers no longer inherit generic CLI banners that should be backend-owned
- Completed TDD slice: route the real CLI through a provider-neutral manager while preserving Ollama as the default backend
Gap: gitAICommit commits raw model output.
gitAICommit touch points:
src/main.rs:114-150— generated text is displayed and committed without cleanupsrc/main.rs:170-183—perform_commit()usesgit commit -m
crabclaw sources to port:
rust/crates/claw-cli/src/main.rs:2310-2312—sanitize_generated_message()rust/crates/claw-cli/src/main.rs:1684-1694— sanitized commit flow usinggit commit --file
Adoption plan:
- Add
sanitize_commit_message()insrc/main.rsor a small helper module - Sanitize before dry-run display and before actual commit
- Prefer
git commit --file <tempfile>overgit commit -mso multi-line messages remain intact - Extend the sanitizer slightly beyond
crabclawto strip common LLM preambles if needed
Gap: gitAICommit uses file-name/stat summaries only, with rough line-cost heuristics.
gitAICommit touch points:
src/formatting/prompt.rs:32-128— staged/unstaged summary assembly + heuristic truncationsrc/git/collector.rs:121-157— collectsgit diff --numstat, not hunkssrc/git/collector.rs:160-209— collects file change list
crabclaw sources to port:
rust/crates/claw-cli/src/main.rs:1709-1715— usesgit diff --statin prompt constructionrust/crates/claw-cli/src/main.rs:2301-2308—truncate_for_prompt()
Adoption plan:
- Keep
gitAICommit's staged/unstaged grouping and file tagging - Port
truncate_for_prompt()for deterministic character-bound truncation - Add an optional hunk mode that collects real staged diff content
- Sort/prioritize files by change magnitude before truncation
- Preserve config/test tagging in the prompt output
Gap: gitAICommit does not know the repository default branch.
gitAICommit touch points:
src/git/collector.rs:232-245— only current branch detection exists- future push/PR workflow in
src/main.rs
crabclaw sources to port:
rust/crates/commands/src/lib.rs:917-935—detect_default_branch()rust/crates/commands/src/lib.rs:974-995—branch_exists()+current_branch()
Adoption plan:
-
Add a
git::detect_default_branch()utility -
Use it for PR base selection
-
Use it for deciding whether to auto-create a feature branch
-
Use it for the "no branch changes" diff check
-
Completed TDD slice: added real default-branch detection through a local bare
originclonesrc/git/repository.rs— addeddetect_default_branch()plus the narrowgit_stdout(),branch_exists(), andcurrent_branch()helperssrc/git/mod.rs— exportsdetect_default_branchtests/interactive_test.rs— addedtest_detect_default_branch_prefers_origin_head_from_real_remote, which creates a real bare remote, pushes a realmainbranch, clones it, switches to a feature branch, and verifies detection still returnsmain- Leveraged
crabclaw's currentdetect_default_branch()flow from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1189-1203 - Leveraged
crabclaw's helper shape from/home/npiesco/crabclaw/rust/crates/commands/src/lib.rs:1246-1270
-
Next TDD target: thread detected default branch into PR creation so
gh pr createtargets the repository base branch explicitly instead of relying on GitHub CLI defaults
Gap: gitAICommit has no branch management.
gitAICommit touch points:
- future push/PR workflow in
src/main.rs - possibly a new helper in
src/git/
crabclaw sources to port:
rust/crates/commands/src/lib.rs:1016-1050—build_branch_name()+slugify()
Adoption plan:
- Port
slugify()directly - Adapt
build_branch_name()to use commit subject or PR title as the hint - Add
--branch <name>override - Only auto-create when current branch equals detected default branch
Gap: --template exists but is ignored.
gitAICommit touch points:
src/cli/args.rs:183-195—template: Option<PathBuf>src/main.rs:61—PromptBuilder::new(...)receives no template inputsrc/formatting/prompt.rs:11-18— constructor always loadsdefault_template()src/formatting/prompt.rs:130-150— hardcoded default template
crabclaw sources to port/adapt:
- No direct template-loader exists in
crabclaw - Best reusable pattern is limited to prompt string assembly:
rust/crates/claw-cli/src/main.rs:1708-1715— build prompt from structured partsrust/crates/claw-cli/src/main.rs:2301-2308— trim prompt payloads before sending
Adoption plan:
- Change
PromptBuilder::new(...)to accept an optional template string/path - Load
--templateinsrc/main.rsbefore building the prompt - Keep
{CONTEXT}as the required placeholder in external templates - Optionally support named templates later, but first fix the broken file-path case
Gap: core git/CLI paths remain lightly tested.
gitAICommit undertested code:
src/git/collector.rs:77-293—GitCollectorsrc/git/status.rs:14-55—GitStatus::parse()src/git/diff.rs:20-57—DiffInfo::parse()src/git/files.rs:24-76—FileChange::parse_list()/parse_line()src/main.rs:71-155— staging, dry-run, confirm, commit flowsrc/ollama/manager.rs— lifecycle behavior
crabclaw sources to port:
rust/crates/commands/src/lib.rs:1669-1748— temp repo helpers:temp_dir(),env_lock(),run_command(),init_git_repo(),init_bare_repo()rust/crates/commands/src/lib.rs:1747-1760—write_fake_gh()rust/crates/commands/src/lib.rs:2421-2510— real repo integration tests for commit and commit+push+PRrust/crates/api/src/client.rs:128-147andrust/crates/api/src/providers/mod.rs:221-245— simple provider selection unit test style
Adoption plan:
- Add a shared test helper module for temp git repos
- Port the fake
ghbinary pattern for future PR tests - Add parser tests for rename/copy/unmerged cases
- Add integration tests for
--dry-run,--add-unstaged, and real commit creation - Set
user.name/user.emailinside test repos explicitly
Gap: gitAICommit has no explicit way to capture intent beyond git metadata.
gitAICommit touch points:
src/cli/args.rs— add a new--context <TEXT>flagsrc/formatting/prompt.rs:22-99— include user context in prompt rendering
crabclaw sources to port/adapt:
rust/crates/claw-cli/src/main.rs:2273-2299—recent_user_context()rust/crates/claw-cli/src/main.rs:1710-1714— PR prompt includesContext hint: ...rust/crates/claw-cli/src/main.rs:1680-1684— commit prompt includes extra context text
Adoption plan:
- Add a plain
--context "what changed and why"flag - Render that context ahead of diff data in the prompt
- Keep this simple;
crabclaw's session history logic is not directly portable, but the prompt shape is
| Priority | Feature | Effort | Impact | Best crabclaw source |
|---|---|---|---|---|
| P0 | Fix --template |
S | Medium | claw-cli/src/main.rs:1708-1715 |
| P0 | AI output sanitization | S | High | claw-cli/src/main.rs:2310-2312 |
| P1 | Test coverage gaps | M | High | commands/src/lib.rs:1669-1760, 2421-2510 |
| P1 | Multiple LLM providers | L | High | api/src/providers/mod.rs:12-24, api/src/client.rs:21-90 |
| P2 | PR title+body generation | M | High | claw-cli/src/main.rs:1708-1738, 2314-2322 |
| P2 | Default branch detection | S | Medium | commands/src/lib.rs:917-935 |
| P2 | --context flag |
S | Medium | claw-cli/src/main.rs:1680-1684, 2273-2299 |
| P3 | Combined commit+push+PR flow | L | High | commands/src/lib.rs:810-915 |
| P3 | Auto branch name generation | S | Medium | commands/src/lib.rs:1016-1050 |
| P3 | Smarter diff context | M | Medium | claw-cli/src/main.rs:2301-2308 |
| P3 | Commit message style parity with crabclaw |
M | High | claw-cli/src/main.rs:1680-1715, 2301-2312 |
-
P0 Fix
--template- Added a real file-backed template loading path in
gitAICommit:src/formatting/prompt.rs— addedPromptBuilder::from_template_file()and validated{CONTEXT}placeholder usagesrc/main.rs— now routesargs.templateinto the prompt builder instead of always usingPromptBuilder::new(...)
- Red test:
tests/prompt_builder_test.rs—test_prompt_builder_uses_custom_template_file
- Validation:
- focused red/green test passed
- full
cargo testpassed cargo fmt --allpassedcargo clippy --all-targets --all-features -- -D warningspassed- second full
cargo testpassed after lint fixes cargo build --releasepassed
- Porting note:
- leveraged
crabclawprompt assembly shape fromnpiesco/crabclaw/rust/crates/claw-cli/src/main.rs:1708-1715 - reused the existing
gitAICommit{CONTEXT}substitution path rather than inventing a second prompt system
- leveraged
- Added a real file-backed template loading path in
-
P0 AI output sanitization
- Added a shared sanitized commit path in
gitAICommit:src/commit.rs— addedsanitize_commit_message()andcommit_message_to_repo()src/lib.rs— exported the new commit helpers for integration tests and reusesrc/main.rs— now sanitizes generated text before preview/dry-run and commits viagit commit --file
- Red test:
tests/commit_sanitization_test.rs—test_commit_message_is_sanitized_before_commit
- Validation:
- focused red/green test passed
- full
cargo testpassed cargo fmt --allpassedcargo clippy --all-targets --all-features -- -D warningspassed- second full
cargo testpassed after lint cargo build --releasepassed
- Porting note:
- leveraged
crabclawsanitizer behavior fromnpiesco/crabclaw/rust/crates/claw-cli/src/main.rs:2310-2312—sanitize_generated_message() - leveraged
crabclaw's temp-file commit flow fromnpiesco/crabclaw/rust/crates/claw-cli/src/main.rs:1684-1694instead of keepinggit commit -m - extended that port narrowly to strip common LLM preambles like
Here is the commit message:because the red integration test covered that real failure mode
- leveraged
- Remaining gap:
- this ports sanitization, not full message-style parity
gitAICommitstill lackscrabclaw's tighter prompt shaping and smarter diff/context trimming, so model output can still drift into chatty or overlong commit text- exact
crabclawsources still to port for style consistency:npiesco/crabclaw/rust/crates/claw-cli/src/main.rs:1680-1715— commit prompt shaping and structured context assemblynpiesco/crabclaw/rust/crates/claw-cli/src/main.rs:2301-2308— smarter diff truncation before generation
- Added a shared sanitized commit path in
-
P1 Test coverage gaps
- Completed TDD slice: real staged rename coverage
tests/interactive_test.rs— addedtest_collect_all_reports_staged_rename_with_old_and_new_pathssrc/git/status.rs— fixedGitStatus::parse()so porcelain rename/copy entries record the destination path instaged_files
- Completed TDD slice: merge conflicts count as real repository changes
tests/interactive_test.rs— addedtest_collect_all_treats_merge_conflicts_as_non_empty_changessrc/git/status.rs— extendedGitStatus::parse()to capture porcelain unmerged states inunmerged_filessrc/git/collector.rs— updatedGitInfo::is_empty()so conflicted repos are not treated as empty
- Completed TDD slice: deduplicate diff stats when one file has both staged and unstaged edits
tests/interactive_test.rs— addedtest_collect_all_deduplicates_diff_stats_for_staged_and_unstaged_same_filesrc/git/diff.rs— updatedDiffInfo::parse()to merge repeated filenames into one aggregatedFileStatentry before computing totals
- Completed TDD slice: display untracked files only once in repository analysis output
tests/interactive_test.rs— addedtest_git_info_display_lists_untracked_files_oncesrc/git/collector.rs— updatedGitInfo::display()to rely onGitStatus::display()for untracked output instead of appending a duplicate section
- Completed TDD slice:
--add-unstagedstages deleted-only repos in the real CLI flowtests/interactive_test.rs— addedtest_add_unstaged_flag_stages_deleted_files_in_main_flowsrc/main.rs— updated the--add-unstagedgate so deleted files triggerstage_all_unstaged()alongside modified and untracked files
- Completed TDD slice: detect staged copies with original and new paths
tests/interactive_test.rs— addedtest_collect_all_reports_staged_copy_with_old_and_new_pathssrc/git/collector.rs— updatedget_file_changes()to request git rename/copy detection with--find-copies-harder, so staged copies are emitted asC...records instead of plain addssrc/git/files.rs— reused the existingFileChange::parse_line()support forChangeType::Copiedandold_pathpreservation rather than introducing a second copy parser
- Validation:
- focused red/green test passed
- full
cargo testpassed cargo fmt --allpassedcargo clippy --all-targets --all-features -- -D warningspassed- second full
cargo testpassed after lint cargo build --releasepassed
- Porting note:
- leveraged
crabclaw's real temp-repo integration testing style fromnpiesco/crabclaw/rust/crates/commands/src/lib.rs:1669-1748, specifically the helper-driven repo setup pattern behindtemp_dir(),env_lock(),run_command(), andinit_git_repo() - followed the same behavior-first repo test shape used by
npiesco/crabclaw/rust/crates/commands/src/lib.rs:2421-2510instead of adding another synthetic parser-only test
- leveraged
- Completed TDD slice: real staged rename coverage
-
P1 Multiple LLM providers
-
P2 PR title+body generation
-
P2 Default branch detection
-
P2
--contextflag -
P3 Combined commit+push+PR flow
-
P3 Auto branch name generation
-
P3 Smarter diff context
-
P3 Commit message style parity with
crabclaw- Goal:
- make generated commit messages consistently match
crabclaw's tighter style constraints instead of only sanitizing obviously bad output afterward
- make generated commit messages consistently match
- Completed TDD slice: prefer the conventional-commit line from chatty AI output
tests/commit_sanitization_test.rs— addedtest_commit_message_prefers_conventional_commit_from_chatty_outputsrc/commit.rs— extendedsanitize_commit_message()to extract the first conventional-commit-style subject line from mixed prose output before writing the commitsrc/commit.rs— added narrow conventional-commit detection sofix: ...andtype(scope): ...subjects are kept while explanatory chatter is dropped
- Completed TDD slice: default prompt uses staged-only commit context
tests/interactive_test.rs— addedtest_prompt_uses_staged_diff_summary_and_excludes_unstaged_detailstests/interactive_test.rs— updated the older prompt integration assertion to match staged-only commit behaviortests/prompt_builder_test.rs— updated prompt-builder expectations from the old broad template to the narrower staged-only commit contractsrc/formatting/prompt.rs— changedPromptBuilder::build()to include only staged file changes and staged diff statistics in the default commit promptsrc/formatting/prompt.rs— replaced the old generic template with a tightercrabclaw-style commit instruction focused on staged diff summary input
- Completed TDD slice: mark truncated staged prompt context explicitly
tests/interactive_test.rs— addedtest_prompt_marks_truncated_staged_context_when_limits_are_exceededsrc/formatting/prompt.rs— updatedadd_file_changes_to_context()to append…[truncated]when the staged prompt context exceeds configured limitssrc/formatting/prompt.rs— kept the existing structured section truncation flow, but aligned the truncation signal withcrabclawso prompt consumers can see that input was intentionally shortened
- Completed TDD slice: deterministically truncate staged prompt context by character budget
tests/interactive_test.rs— addedtest_prompt_truncates_single_oversized_staged_entry_by_character_budgetsrc/formatting/prompt.rs— replaced the rough staged entry size heuristic with atruncate_for_prompt()-style character-budget truncation pass over the rendered staged contextsrc/formatting/prompt.rs— kept the explicit…[truncated]marker and made oversized single entries truncate correctly instead of slipping past the budget because they counted as only one estimated file
- Completed TDD slice: remove extra default prompt scaffolding while preserving richer custom-template context
tests/prompt_builder_test.rs— addedtest_default_prompt_avoids_extra_instruction_block_and_repo_metadata_scaffoldingsrc/formatting/prompt.rs— removed the extraRequirements:block from the built-in default commit template so it matchescrabclaw's tighterrun_commit()contract more closelysrc/formatting/prompt.rs— stopped prependingCurrent branch:andLast commit:metadata in the built-in default prompt path, but preserved that richer context forPromptBuilder::from_template_file(...)and other custom-template renderstests/prompt_builder_test.rs— updated the older default-prompt assertions to reflect the narrower built-in contract while keeping the custom-template metadata assertion intact
- Completed TDD slice: explicit
--contextflag feeds a bounded conversation-context lane into the real CLI prompt flowtests/interactive_test.rs— addedtest_context_flag_is_included_in_default_prompt_via_real_cli_flowtests/cli_args_test.rs— addedtest_context_flagand extended the combined-options parse coveragesrc/cli/args.rs— added--context <TEXT>as a first-class customization flagsrc/main.rs— routedargs.contextinto the default prompt builder path without changing custom-template renderingsrc/formatting/prompt.rs— added an explicitRecent conversation context:lane for the built-in default prompt when user context is provided, bounded by the same prompt truncation helper
- Completed TDD slice: default prompt now follows
crabclaw's compact staged diff-stat contract instead of injecting extra staged-file scaffoldingtests/prompt_builder_test.rs— addedtest_default_prompt_uses_diff_stat_contract_without_extra_staged_file_scaffoldingtests/prompt_builder_test.rs— updated older default-prompt assertions so they validate compact staged diff-stat lines instead of the pre-portStaged changes (will be committed):blocktests/interactive_test.rs— updatedtest_prompt_prefers_staged_changes_onlyto assert staged diff summary plus staged filenames without reintroducing the removed scaffoldingsrc/formatting/prompt.rs— split built-in default prompt context from custom-template context so only the default path adopts the tightercrabclawcontractsrc/formatting/prompt.rs— changed the built-in default prompt to render staged diff summary plus compact per-file stat lines (path | +N -M) and to truncate that block on line boundaries with the existing…[truncated]markersrc/formatting/prompt.rs— preserved the richer staged file/change sections for custom templates so--templateusers still get the fuller{CONTEXT}payload
- Completed TDD slice: prose-only weak-model output now collapses to a single subject line instead of committing the whole paragraph block
tests/commit_sanitization_test.rs— addedtest_commit_message_falls_back_to_first_meaningful_line_when_model_returns_prosesrc/commit.rs— kept conventional-commit extraction as the primary path, but added a fallback to the first meaningful non-empty line when no conventional subject is presentsrc/commit.rs— preserved the temp-filegit commit --fileflow; the change stays strictly in the sanitizer boundary and does not invent a new commit pipeline
- Completed TDD slice: malformed single-line weak-model label echoes are stripped before commit
tests/commit_sanitization_test.rs— addedtest_commit_message_strips_malformed_single_line_prompt_labelssrc/commit.rs— added a narrow single-line cleanup pass that strips malformed... commit message:prefixes and leading bracketed prompt-label remnants before the existing fallback logic runssrc/commit.rs— kept conventional-commit extraction and first-line fallback unchanged as the primary decision path; this only removes weak-model prompt-label noise at the sanitizer boundary
- Completed TDD slice: glossary-style all-caps weak-model prefixes are stripped before commit
tests/commit_sanitization_test.rs— addedtest_commit_message_strips_glossary_style_all_caps_prefixessrc/commit.rs— extended the single-line cleanup pass withstrip_glossary_style_prefix()so outputs likeLORE (Logical OR Evaluator) [PRINCIPLE #3]: ...collapse to the usable subject instead of being committed verbatimsrc/commit.rs— kept this as a narrow sanitizer-boundary extension on top of the existing conventional-commit extraction and first-meaningful-line fallback, matchingcrabclaw's final-write cleanup placement rather than inventing a new generation path
- Completed TDD slice: trailing echoed prompt scaffolding is stripped from single-line conventional subjects
tests/commit_sanitization_test.rs— addedtest_commit_message_strips_trailing_echoed_prompt_scaffolding_from_single_line_subjectsrc/commit.rs— extended the single-line cleanup path withstrip_trailing_echoed_prompt_scaffolding()so outputs likefeat: ... - [Commit Message]: ...keep the conventional subject and drop the echoed prompt tailsrc/commit.rs— kept the change at the same final sanitizer boundary used bycrabclaw'ssanitize_generated_message()innpiesco/crabclaw/rust/crates/claw-cli/src/main.rs:2310-2312, instead of inventing another prompt or generation flow
- Completed TDD slice: bracket-only boilerplate is rejected instead of being committed
tests/commit_sanitization_test.rs— addedtest_commit_message_rejects_bracket_only_boilerplate_after_sanitizationsrc/commit.rs— addedlooks_like_bracket_only_boilerplate()and madestrip_single_line_prompt_labels()collapse single-line bracket-only outputs like[Lorem Ipsum]to empty before conventional-subject extractionsrc/commit.rs— reused the existinggenerated commit message was emptyguard incommit_message_to_repo()so the real commit path now fails cleanly instead of creating a garbage commit, keeping the fix at the same final-write boundarycrabclawuses innpiesco/crabclaw/rust/crates/claw-cli/src/main.rs:2310-2312
- Completed TDD slice: raw git commit-hash metadata is rejected instead of being committed
tests/commit_sanitization_test.rs— addedtest_commit_message_rejects_raw_git_commit_hash_metadatasrc/commit.rs— addedlooks_like_raw_git_metadata()and madestrip_single_line_prompt_labels()collapse single-line outputs likecommit 3d89b7c2f5a6...to empty before fallback selectionsrc/commit.rs— reused the samegenerated commit message was emptyguard incommit_message_to_repo(), preservingcrabclaw's final-write sanitizer boundary fromnpiesco/crabclaw/rust/crates/claw-cli/src/main.rs:2310-2312instead of adding another prompt-time workaround
- Completed TDD slice: literal
git commit -mshell wrappers are rejected instead of being committedtests/commit_sanitization_test.rs— addedtest_commit_message_rejects_literal_git_commit_shell_wrappersrc/commit.rs— addedlooks_like_git_commit_shell_wrapper()and madestrip_single_line_prompt_labels()collapse single-line outputs likegit commit -m "..."to empty before fallback selectionsrc/commit.rs— reused the samegenerated commit message was emptyguard incommit_message_to_repo(), preservingcrabclaw's final-write sanitizer boundary fromnpiesco/crabclaw/rust/crates/claw-cli/src/main.rs:2310-2312instead of adding another generation path
- Primary
gitAICommittouch points:src/formatting/prompt.rs— tighten prompt instructions and output contractsrc/main.rs— keep the generation path wired through the stricter prompt buildersrc/commit.rs— preserve sanitizer as a safety net, not the primary style control
crabclawfiles/functions to leverage instead of reinventing:npiesco/crabclaw/rust/crates/claw-cli/src/main.rs:1680-1715— commit prompt construction pathnpiesco/crabclaw/rust/crates/claw-cli/src/main.rs:1795— exact defaultrun_commit()prompt wording centered on staged diff summary inputnpiesco/crabclaw/rust/crates/claw-cli/src/main.rs:1862-1867—run_commit()uses onlygit diff --cached --statplus recent context, which is the compact default prompt contract now mirrored ingitAICommitnpiesco/crabclaw/rust/crates/claw-cli/src/main.rs:1795and1842-1849—Recent conversation context:lane shape and bounded context injectionnpiesco/crabclaw/rust/crates/claw-cli/src/main.rs:2301-2308—truncate_for_prompt()and its explicit…[truncated]marker used before generationnpiesco/crabclaw/rust/crates/claw-cli/src/main.rs:2310-2312— sanitizer remains the last-line cleanup layer
- Next TDD target:
- tighten sanitizer handling for remaining weak-model outputs that echo literal shell command wrappers like
git commit -m "..."as the entire generated "commit message"
- tighten sanitizer handling for remaining weak-model outputs that echo literal shell command wrappers like
- Validation:
- focused red/green test passed
- full
cargo testpassed cargo fmt --allpassedcargo clippy --all-targets --all-features -- -D warningspassed- second full
cargo testpassed after lint cargo build --releasepassed
- Goal: