From 463b0cb148373d0cff13401bd155f6815eab00d2 Mon Sep 17 00:00:00 2001 From: Kavitha Kesavalu Date: Sat, 3 Jan 2026 21:59:38 -0500 Subject: [PATCH 1/4] fix: unit tests --- src/commands/applywithllm.test.ts | 47 ++++++++++++------------------- src/services/llm.test.ts | 4 ++- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/src/commands/applywithllm.test.ts b/src/commands/applywithllm.test.ts index e3826191..e9efe375 100644 --- a/src/commands/applywithllm.test.ts +++ b/src/commands/applywithllm.test.ts @@ -3,11 +3,9 @@ import { IMigrationContext } from '../migration-context'; import mockAdapter from '../adapters/adapter.mock'; import mockLogger from '../logger/logger.mock'; import * as llmService from '../services/llm'; -import * as gitDiff from '../util/git-diff'; import fs from 'fs-extra'; jest.mock('../services/llm'); -jest.mock('../util/git-diff'); jest.mock('fs-extra'); // Mock process.exit globally - don't throw, just return @@ -51,30 +49,16 @@ describe('applywithllm command', () => { // Default mock implementations mockAdapter.getRepoDir.mockReturnValue('/tmp/repo1'); + mockAdapter.resetChangedFiles.mockResolvedValue(undefined); (fs.pathExists as jest.Mock).mockResolvedValue(true); + (fs.writeFile as jest.Mock).mockResolvedValue(undefined); (llmService.readFilesForContext as jest.Mock).mockResolvedValue([ { path: 'file1.ts', content: 'const x = 1;' }, ]); - (gitDiff.validateDiff as jest.Mock).mockResolvedValue({ - valid: true, - errors: [], - warnings: [], - }); - (gitDiff.applyDiff as jest.Mock).mockResolvedValue(undefined); - (gitDiff.parseDiffStats as jest.Mock).mockReturnValue({ - additions: 1, - deletions: 1, - }); - (gitDiff.extractFilePaths as jest.Mock).mockReturnValue(['file1.ts']); const mockProvider = { callLLM: jest.fn().mockResolvedValue({ - diffs: `--- a/file1.ts -+++ b/file1.ts -@@ -1 +1 @@ --const x = 1; -+const x = 2; -`, + diffs: 'const x = 2;', }), }; (llmService.getLLMProvider as jest.Mock).mockReturnValue(mockProvider); @@ -121,22 +105,27 @@ describe('applywithllm command', () => { await applywithllm(mockContext, options, prompt); - // validateDiff should be called but not applyDiff - expect(gitDiff.validateDiff).toHaveBeenCalled(); + expect(mockLogger.info).toHaveBeenCalled(); }); - it('should reset repo on validation failure', async () => { + it('should handle empty LLM response', async () => { const prompt = '@files file1.ts\nRefactor this file'; - (gitDiff.validateDiff as jest.Mock).mockResolvedValueOnce({ - valid: false, - errors: ['Patch does not apply'], - warnings: [], - }); + const mockProvider = { + callLLM: jest.fn().mockResolvedValue({ + diffs: '', + }), + }; + (llmService.getLLMProvider as jest.Mock).mockReturnValue(mockProvider); await applywithllm(mockContext, options, prompt); - // Should reset the repo on failure - expect(mockAdapter.resetChangedFiles).toHaveBeenCalled(); + // Should not write the file content when response is empty (only the response JSON is written) + // Check that writeFile was only called once for the response JSON, not for the actual file + const writeFileCalls = (fs.writeFile as jest.Mock).mock.calls; + const fileContentWriteCalls = writeFileCalls.filter( + (call) => call[0] === '/tmp/repo1/file1.ts' + ); + expect(fileContentWriteCalls.length).toBe(0); }); it('should handle LLM API errors gracefully', async () => { diff --git a/src/services/llm.test.ts b/src/services/llm.test.ts index cece3287..660a463f 100644 --- a/src/services/llm.test.ts +++ b/src/services/llm.test.ts @@ -16,7 +16,9 @@ describe('LLM Service', () => { describe('getLLMProvider', () => { it('should throw error when API key is not provided', () => { - expect(() => getLLMProvider()).toThrow('Groq API key not provided'); + expect(() => getLLMProvider()).toThrow( + 'No LLM API key found. Set OPENAI_API_KEY or GROQ_API_KEY environment variable.' + ); }); it('should return provider with provided API key', () => { From cba61742a7b370ac48843088dece0380be63f15e Mon Sep 17 00:00:00 2001 From: Kavitha Kesavalu Date: Sat, 3 Jan 2026 22:02:32 -0500 Subject: [PATCH 2/4] Update src/commands/applywithllm.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/commands/applywithllm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/applywithllm.ts b/src/commands/applywithllm.ts index 0cb62308..66be36fc 100644 --- a/src/commands/applywithllm.ts +++ b/src/commands/applywithllm.ts @@ -211,7 +211,7 @@ export default async ( // Original repo-based mode const repos = migration.repos || []; - console.log('Applying migration with LLM to repos:', repos); + logger.info('Applying migration with LLM to repos:', repos); if (!process.env.GROQ_API_KEY && !process.env.OPENAI_API_KEY) { logger.error('Either GROQ_API_KEY or OPENAI_API_KEY must be set'); From 44a2473e428b73f1c0bd3b02094d9dddb8415418 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 4 Jan 2026 03:02:56 +0000 Subject: [PATCH 3/4] Initial plan From a239dfb7bb15a099e58759a7ccc424869d100fc8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 4 Jan 2026 03:06:52 +0000 Subject: [PATCH 4/4] docs: fix Groq configuration with correct API key format and model names Co-authored-by: kavitha186 <17735710+kavitha186@users.noreply.github.com> --- APPLYWITHLLM_QUICKSTART.md | 40 +++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/APPLYWITHLLM_QUICKSTART.md b/APPLYWITHLLM_QUICKSTART.md index 0d0f4dc7..652a42e5 100644 --- a/APPLYWITHLLM_QUICKSTART.md +++ b/APPLYWITHLLM_QUICKSTART.md @@ -7,19 +7,21 @@ The `applywithllm` command is now part of Shepherd. It has been fully integrated ### Prerequisites 1. **Node.js 18+** (for built-in fetch support) -2. **OpenAI API Key** - Get one from [platform.openai.com](https://platform.openai.com/api-keys) +2. **LLM API Key** - Get one from [platform.openai.com](https://platform.openai.com/api-keys) (OpenAI) or [console.groq.com](https://console.groq.com/keys) (Groq) 3. **Git** - Must be installed on your system ### Configuration -Set your OpenAI API key as an environment variable: +Set your LLM API key as an environment variable (choose OpenAI or Groq): ```bash -# Export the API key (add to .bashrc or .zshrc for persistence) -export GROQ_API_KEY="sk-your-openai-api-key-here" +# Option 1: OpenAI (recommended for best results) +export OPENAI_API_KEY="sk-your-openai-key-here" +export OPENAI_MODEL="gpt-4" # or gpt-4-turbo, gpt-3.5-turbo, etc. (defaults to gpt-3.5-turbo) -# Optionally set the model (defaults to gpt-4) -export GROQ_MODEL="gpt-4-turbo" # or gpt-4, gpt-3.5-turbo, etc. +# Option 2: Groq (faster, open-source models) +export GROQ_API_KEY="gsk_your-groq-key-here" +export GROQ_MODEL="llama-3.3-70b-versatile" # or mixtral-8x7b-32768, etc. (defaults to llama-3.3-70b-versatile) ``` ## Command Syntax @@ -51,7 +53,7 @@ shepherd applywithllm my-migration "@files src/utils.ts Convert callback functio What happens: 1. Reads `src/utils.ts` from each checked-out repository -2. Sends it to OpenAI with your refactoring instructions +2. Sends it to the LLM (OpenAI or Groq) with your refactoring instructions 3. Receives unified diffs back 4. Validates diffs using `git apply --check` 5. Applies the changes to your repositories @@ -103,7 +105,7 @@ INPUT (Natural Language Prompt + Files) ↓ 3. READ: Load file contents ↓ -4. CALL LLM: Send prompt + context to OpenAI +4. CALL LLM: Send prompt + context to LLM provider (OpenAI or Groq) ↓ 5. VALIDATE DIFFS: Check patches with git apply --check ↓ @@ -130,7 +132,7 @@ Repositories are **automatically reset** on failure, ensuring no partial changes Migrate from React class components to hooks: ```bash -export GROQ_API_KEY="sk-..." +export OPENAI_API_KEY="sk-..." # or GROQ_API_KEY="gsk_..." for Groq shepherd applywithllm react-hooks-migration "@files src/components/UserProfile.tsx,src/components/Header.tsx \ Convert these React class components to functional components with hooks. \ Use useState for state management and useEffect for lifecycle methods." @@ -219,11 +221,13 @@ shepherd pr my-migration ## Troubleshooting -### "GROQ_API_KEY environment variable is not set" +### "No LLM API key found" error ```bash -# Solution: Export your API key -export GROQ_API_KEY="sk-your-key" +# Solution: Export either OpenAI or Groq API key +export OPENAI_API_KEY="sk-your-openai-key" # OpenAI format starts with "sk-" +# OR +export GROQ_API_KEY="gsk_your-groq-key" # Groq format starts with "gsk_" ``` ### "Diff validation failed" @@ -245,10 +249,14 @@ Ensure: ### "LLM API error" -- Check your API key is valid -- Check your OpenAI account has credits +- Check your API key is valid and in the correct format: + - OpenAI keys start with `sk-` + - Groq keys start with `gsk_` +- Check your account has credits/quota - Check network connectivity -- Verify GROQ_MODEL is valid (gpt-4, gpt-3.5-turbo, etc.) +- Verify model name is valid: + - OpenAI: gpt-4, gpt-4-turbo, gpt-3.5-turbo, etc. + - Groq: llama-3.3-70b-versatile, mixtral-8x7b-32768, etc. ## Best Practices @@ -307,7 +315,7 @@ Plan accordingly for batch migrations on many repositories. Current limitations: -- Single LLM provider (OpenAI only, for now) +- Two LLM providers supported (OpenAI and Groq) - Sequential processing (one repo at a time) - No caching between runs