diff --git a/src/commands/suggest.ts b/src/commands/suggest.ts index a5adde7..a65fae3 100644 --- a/src/commands/suggest.ts +++ b/src/commands/suggest.ts @@ -98,6 +98,7 @@ export async function suggestCommand( commit?: boolean; autoCommit?: boolean; verbose?: boolean; + showDiff?: boolean; model?: string; stream?: boolean; dryRun?: boolean; @@ -176,9 +177,10 @@ export async function suggestCommand( } const profile = await buildProfile(config.historySize); + const preview = options.dryRun || options.showDiff ? truncateDiff(diffResult.diff, config.maxDiffSize) : undefined; if (options.dryRun) { - const { diff: truncatedDiff, info: truncation } = truncateDiff(diffResult.diff, config.maxDiffSize); + const { diff: truncatedDiff, info: truncation } = preview!; const vars = { diff: truncatedDiff, profile: formatProfile(profile), @@ -199,6 +201,19 @@ export async function suggestCommand( return; } + if (options.showDiff) { + const { diff: truncatedDiff, info: truncation } = preview!; + console.log(pc.bold('Diff being analyzed:')); + console.log(pc.dim(truncatedDiff)); + console.log(''); + if (truncation.wasTruncated) { + console.log(pc.dim('The diff above is truncated to match maxDiffSize.')); + console.log(''); + } + } + + const analysisDiff = options.showDiff ? preview!.diff : diffResult.diff; + const analysisTruncation = options.showDiff ? preview!.info : undefined; let apiKey: string; try { apiKey = assertApiKeyAvailable(config); @@ -208,7 +223,7 @@ export async function suggestCommand( } while (true) { let suggestions: Suggestion[]; - let truncation: TruncationInfo | undefined; + let generatedTruncation: TruncationInfo | undefined; let model: string; if (options.stream) { @@ -225,9 +240,16 @@ export async function suggestCommand( model = config.model; let accumulated = ''; try { - for await (const event of generateSuggestionsStream(config, diffResult.diff, profile, apiKey, streamProvider)) { + for await (const event of generateSuggestionsStream( + config, + analysisDiff, + profile, + apiKey, + streamProvider, + analysisTruncation, + )) { if (event.kind === 'meta') { - truncation = event.truncation; + generatedTruncation = event.truncation; continue; } @@ -265,9 +287,9 @@ export async function suggestCommand( genSpinner.start('Generating commit suggestions...'); try { - const result = await generateSuggestions(config, diffResult.diff, profile, apiKey); + const result = await generateSuggestions(config, analysisDiff, profile, apiKey, analysisTruncation); suggestions = result.suggestions; - truncation = result.truncation; + generatedTruncation = result.truncation; model = result.model; genSpinner.stop(pc.green('Suggestions generated:')); } catch (err) { @@ -279,11 +301,11 @@ export async function suggestCommand( } if (options.verbose) { - showVerboseInfo(model, profile, truncation); + showVerboseInfo(model, profile, generatedTruncation); } - if (truncation) { - showTruncationWarning(truncation); + if (generatedTruncation) { + showTruncationWarning(generatedTruncation); } if (!options.stream) { diff --git a/src/index.ts b/src/index.ts index 2667d0f..de93f8a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -72,6 +72,7 @@ program .option('--commit', 'Commit the selected suggestion', false) .option('-y, --yes', 'Automatically select the first suggestion and skip prompts') .option('-v, --verbose', 'Print diagnostic information about the suggestion request') + .option('-d, --show-diff', 'Print the diff content that will be sent to the LLM') .option('-m, --model ', 'Override the configured LLM model for this invocation') .option('--stream', 'Stream suggestions as they are generated (progressive output)') .option('-n, --dry-run', 'Show the LLM input without generating suggestions') @@ -83,6 +84,7 @@ program commit: options.commit, autoCommit: Boolean(options.yes || options.auto || globalOpts.yes || globalOpts.auto), verbose: Boolean(options.verbose), + showDiff: Boolean(options.showDiff), model: options.model, stream: Boolean(options.stream), dryRun: Boolean(options.dryRun), diff --git a/src/llm/client.ts b/src/llm/client.ts index e1f32d3..6cdeac5 100644 --- a/src/llm/client.ts +++ b/src/llm/client.ts @@ -38,6 +38,7 @@ export async function generateSuggestions( diff: string, profileParam?: StyleProfile, apiKeyParam?: string, + precomputedTruncation?: TruncationInfo, ): Promise<{ suggestions: Suggestion[]; profile: StyleProfile; @@ -46,8 +47,9 @@ export async function generateSuggestions( }> { const profile = profileParam ?? (await buildProfile(config.historySize)); - // Truncate diff if it exceeds the configured limit - const { diff: truncatedDiff, info: truncation } = truncateDiff(diff, config.maxDiffSize); + const { diff: truncatedDiff, info: truncation } = precomputedTruncation + ? { diff, info: precomputedTruncation } + : truncateDiff(diff, config.maxDiffSize); const branch = getBranchName(); const profileStr = formatProfile(profile); @@ -113,10 +115,13 @@ export async function* generateSuggestionsStream( profileParam?: StyleProfile, apiKeyParam?: string, provider?: Provider, + precomputedTruncation?: TruncationInfo, ): AsyncGenerator { const profile = profileParam ?? (await buildProfile(config.historySize)); - const { diff: truncatedDiff, info: truncation } = truncateDiff(diff, config.maxDiffSize); + const { diff: truncatedDiff, info: truncation } = precomputedTruncation + ? { diff, info: precomputedTruncation } + : truncateDiff(diff, config.maxDiffSize); const branch = getBranchName(); const profileStr = formatProfile(profile); diff --git a/tests/e2e/suggest-smoke.test.mjs b/tests/e2e/suggest-smoke.test.mjs index b0b8054..c59387e 100644 --- a/tests/e2e/suggest-smoke.test.mjs +++ b/tests/e2e/suggest-smoke.test.mjs @@ -23,6 +23,20 @@ function stripAnsi(text) { return text.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, ''); } +function extractShownDiff(stdout) { + const match = stdout.match( + /Diff being analyzed:\n([\s\S]*?)\n\n(?:The diff above is truncated|Streaming suggestions|Suggestions generated:)/, + ); + assert.ok(match, `Could not find shown diff in stdout:\n${stdout}`); + return match[1]; +} + +function extractPromptDiff(content) { + const match = content.match(/```diff\n([\s\S]*)\n```\n\nReturn exactly 3 options/); + assert.ok(match, `Could not find prompt diff in request:\n${content}`); + return match[1]; +} + function runSuggestUntil(args, { cwd, env, text }) { return new Promise((resolve, reject) => { const child = spawn(process.execPath, [join(process.cwd(), 'dist/index.js'), ...args], { @@ -52,7 +66,9 @@ function runSuggestUntil(args, { cwd, env, text }) { } } }); - child.stderr.on('data', (chunk) => { stderr += chunk.toString(); }); + child.stderr.on('data', (chunk) => { + stderr += chunk.toString(); + }); child.on('error', (err) => { settled = true; clearTimeout(timeout); @@ -159,10 +175,12 @@ test('suggest smoke test boots the CLI, loads config, and prints suggestions', a const parsed = JSON.parse(body); requests.push(parsed); res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - model: parsed.model, - choices: [{ message: { content: '1. feat: add smoke test coverage\n2. docs: refresh quickstart examples' } }], - })); + res.end( + JSON.stringify({ + model: parsed.model, + choices: [{ message: { content: '1. feat: add smoke test coverage\n2. docs: refresh quickstart examples' } }], + }), + ); return; } @@ -177,20 +195,30 @@ test('suggest smoke test boots the CLI, loads config, and prints suggestions', a await writeFile( join(configDir, 'config.json'), - JSON.stringify({ - provider: '__custom__', - model: 'fixture-model', - baseUrl: `http://127.0.0.1:${port}`, - apiKey: 'test-key', - historySize: 5, - }, null, 2), - 'utf8' + JSON.stringify( + { + provider: '__custom__', + model: 'fixture-model', + baseUrl: `http://127.0.0.1:${port}`, + apiKey: 'test-key', + historySize: 5, + }, + null, + 2, + ), + 'utf8', ); await writeFile( join(configDir, 'history.jsonl'), - JSON.stringify({ timestamp: new Date().toISOString(), message: 'feat: add fixture history', diff: '', model: 'fixture-model', provider: '__custom__' }) + '\n', - 'utf8' + JSON.stringify({ + timestamp: new Date().toISOString(), + message: 'feat: add fixture history', + diff: '', + model: 'fixture-model', + provider: '__custom__', + }) + '\n', + 'utf8', ); const child = spawn('node', [join(process.cwd(), 'dist/index.js'), 'suggest'], { @@ -349,13 +377,17 @@ test('suggest reports no changes before checking for an API key', async (t) => { await writeFile( join(configDir, 'config.json'), - JSON.stringify({ - provider: 'openai', - model: 'gpt-4.1', - historySize: 5, - maxDiffSize: 4000, - }, null, 2), - 'utf8' + JSON.stringify( + { + provider: 'openai', + model: 'gpt-4.1', + historySize: 5, + maxDiffSize: 4000, + }, + null, + 2, + ), + 'utf8', ); t.after(async () => { @@ -377,8 +409,12 @@ test('suggest reports no changes before checking for an API key', async (t) => { let stdout = ''; let stderr = ''; - child.stdout.on('data', (chunk) => { stdout += chunk.toString(); }); - child.stderr.on('data', (chunk) => { stderr += chunk.toString(); }); + child.stdout.on('data', (chunk) => { + stdout += chunk.toString(); + }); + child.stderr.on('data', (chunk) => { + stderr += chunk.toString(); + }); const result = await onceExit(child); @@ -401,10 +437,12 @@ test('suggest --model overrides configured model for one invocation and -m is an const parsed = JSON.parse(body); requests.push(parsed); res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - model: parsed.model, - choices: [{ message: { content: '1. feat: add override flag\n2. test: cover model alias' } }], - })); + res.end( + JSON.stringify({ + model: parsed.model, + choices: [{ message: { content: '1. feat: add override flag\n2. test: cover model alias' } }], + }), + ); return; } @@ -419,14 +457,18 @@ test('suggest --model overrides configured model for one invocation and -m is an await writeFile( join(configDir, 'config.json'), - JSON.stringify({ - provider: '__custom__', - model: 'configured-model', - baseUrl: `http://127.0.0.1:${port}`, - apiKey: 'test-key', - historySize: 5, - }, null, 2), - 'utf8' + JSON.stringify( + { + provider: '__custom__', + model: 'configured-model', + baseUrl: `http://127.0.0.1:${port}`, + apiKey: 'test-key', + historySize: 5, + }, + null, + 2, + ), + 'utf8', ); const env = { @@ -437,7 +479,11 @@ test('suggest --model overrides configured model for one invocation and -m is an FORCE_COLOR: '0', }; - const longFlag = await runSuggestUntil(['suggest', '--yes', '--verbose', '--model', 'gpt-4o'], { cwd: repo, env, text: 'Model: gpt-4o' }); + const longFlag = await runSuggestUntil(['suggest', '--yes', '--verbose', '--model', 'gpt-4o'], { + cwd: repo, + env, + text: 'Model: gpt-4o', + }); assert.equal(requests.at(-1).model, 'gpt-4o'); assert.match(longFlag.stdout, /Model: gpt-4o/); @@ -445,6 +491,237 @@ test('suggest --model overrides configured model for one invocation and -m is an assert.equal(requests.at(-1).model, 'claude-3-5-sonnet'); }); +test('suggest --show-diff prints the truncated staged diff before generating suggestions', async (t) => { + const root = await mkdtemp(join(tmpdir(), 'commit-echo-show-diff-staged-')); + const { home, repo, configDir } = await setupRepo(root); + + const requests = []; + const server = createServer(async (req, res) => { + if (req.url === '/chat/completions' && req.method === 'POST') { + let body = ''; + req.setEncoding('utf8'); + for await (const chunk of req) body += chunk; + requests.push(JSON.parse(body)); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end( + JSON.stringify({ + model: 'fixture-model', + choices: [{ message: { content: '1. feat: inspect staged diff' } }], + }), + ); + return; + } + + res.writeHead(404, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'not found' })); + }); + const port = await listen(server); + t.after(async () => { + server.close(); + await rm(root, { recursive: true, force: true }); + }); + + await writeFile( + join(repo, 'README.md'), + ['# fixture', '', ...Array.from({ length: 40 }, (_, i) => `line ${i}`)].join('\n') + '\n', + 'utf8', + ); + execFileSync('git', ['add', 'README.md'], { cwd: repo }); + + await writeFile( + join(configDir, 'config.json'), + JSON.stringify( + { + provider: '__custom__', + model: 'fixture-model', + baseUrl: `http://127.0.0.1:${port}`, + apiKey: 'test-key', + historySize: 5, + maxDiffSize: 120, + }, + null, + 2, + ), + 'utf8', + ); + + const env = { + ...process.env, + HOME: home, + XDG_CONFIG_HOME: join(home, '.config'), + APPDATA: join(home, 'AppData', 'Roaming'), + FORCE_COLOR: '0', + }; + + const result = await runCli(['suggest', '--show-diff', '--yes'], { cwd: repo, env }); + const stdout = stripAnsi(result.stdout); + const stderr = stripAnsi(result.stderr); + + assert.equal(result.code, 0); + assert.match(stdout, /Diff being analyzed:/); + assert.match(stdout, /diff --git a\/README\.md b\/README\.md/); + assert.match(stdout, /\[\.\.\.truncated 1 file\.\.\.\]/); + assert.ok(stdout.indexOf('Diff being analyzed:') < stdout.indexOf('Suggestions generated:')); + assert.match(stdout, /Selected:\s+feat: inspect staged diff/); + assert.match(stderr, /Diff truncated:/); + assert.match(requests.at(-1).messages[1].content, /\[\.\.\.truncated 1 file\.\.\.\]/); + assert.equal(extractPromptDiff(requests.at(-1).messages[1].content), extractShownDiff(stdout)); +}); + +test('suggest --show-diff works with unstaged changes in auto mode', async (t) => { + const root = await mkdtemp(join(tmpdir(), 'commit-echo-show-diff-unstaged-')); + const { home, repo, configDir } = await setupRepo(root); + + const requests = []; + const server = createServer(async (req, res) => { + if (req.url === '/chat/completions' && req.method === 'POST') { + let body = ''; + req.setEncoding('utf8'); + for await (const chunk of req) body += chunk; + requests.push(JSON.parse(body)); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end( + JSON.stringify({ + model: 'fixture-model', + choices: [{ message: { content: '1. feat: inspect unstaged diff' } }], + }), + ); + return; + } + + res.writeHead(404, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'not found' })); + }); + const port = await listen(server); + t.after(async () => { + server.close(); + await rm(root, { recursive: true, force: true }); + }); + + execFileSync('git', ['restore', '--staged', 'README.md'], { cwd: repo }); + + await writeFile( + join(configDir, 'config.json'), + JSON.stringify( + { + provider: '__custom__', + model: 'fixture-model', + baseUrl: `http://127.0.0.1:${port}`, + apiKey: 'test-key', + historySize: 5, + }, + null, + 2, + ), + 'utf8', + ); + + const env = { + ...process.env, + HOME: home, + XDG_CONFIG_HOME: join(home, '.config'), + APPDATA: join(home, 'AppData', 'Roaming'), + FORCE_COLOR: '0', + }; + + const result = await runCli(['suggest', '--show-diff', '--yes'], { cwd: repo, env }); + const stdout = stripAnsi(result.stdout); + + assert.equal(result.code, 0); + assert.equal(result.stderr, ''); + assert.match(stdout, /Diff being analyzed:/); + assert.match(stdout, /diff --git a\/README\.md b\/README\.md/); + assert.match(stdout, /\+updated/); + assert.doesNotMatch(stdout, /Use unstaged changes for suggestions/); + assert.match(stdout, /Selected:\s+feat: inspect unstaged diff/); + assert.match(requests.at(-1).messages[1].content, /\+updated/); +}); + +test('suggest --show-diff uses the truncated diff for streamed suggestions', async (t) => { + const root = await mkdtemp(join(tmpdir(), 'commit-echo-show-diff-stream-')); + const { home, repo, configDir } = await setupRepo(root); + + const requests = []; + const server = createServer(async (req, res) => { + if (req.url === '/chat/completions' && req.method === 'POST') { + let body = ''; + req.setEncoding('utf8'); + for await (const chunk of req) body += chunk; + const parsed = JSON.parse(body); + requests.push(parsed); + + if (parsed.stream) { + res.writeHead(200, { 'Content-Type': 'text/event-stream' }); + res.write('data: {"choices":[{"delta":{"content":"1. feat: streamed diff preview"}}]}\n\n'); + res.write('data: [DONE]\n\n'); + res.end(); + return; + } + + res.writeHead(500, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'expected streaming request' })); + return; + } + + res.writeHead(404, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'not found' })); + }); + const port = await listen(server); + t.after(async () => { + server.close(); + await rm(root, { recursive: true, force: true }); + }); + + await writeFile( + join(repo, 'README.md'), + ['# fixture', '', ...Array.from({ length: 40 }, (_, i) => `stream line ${i}`)].join('\n') + '\n', + 'utf8', + ); + execFileSync('git', ['add', 'README.md'], { cwd: repo }); + + await writeFile( + join(configDir, 'config.json'), + JSON.stringify( + { + provider: '__custom__', + model: 'fixture-model', + baseUrl: `http://127.0.0.1:${port}`, + apiKey: 'test-key', + historySize: 5, + maxDiffSize: 120, + }, + null, + 2, + ), + 'utf8', + ); + + const env = { + ...process.env, + HOME: home, + XDG_CONFIG_HOME: join(home, '.config'), + APPDATA: join(home, 'AppData', 'Roaming'), + FORCE_COLOR: '0', + }; + + const result = await runCli(['suggest', '--show-diff', '--stream', '--yes'], { cwd: repo, env }); + const stdout = stripAnsi(result.stdout); + const stderr = stripAnsi(result.stderr); + const request = requests.at(-1); + + assert.equal(result.code, 0); + assert.match(stdout, /Diff being analyzed:/); + assert.match(stdout, /diff --git a\/README\.md b\/README\.md/); + assert.match(stdout, /\[\.\.\.truncated 1 file\.\.\.\]/); + assert.ok(stdout.indexOf('Diff being analyzed:') < stdout.indexOf('Streaming suggestions')); + assert.match(stdout, /feat: streamed diff preview/); + assert.match(stdout, /Selected:\s+feat: streamed diff preview/); + assert.match(stderr, /Diff truncated:/); + assert.equal(request?.stream, true); + assert.match(request.messages[1].content, /\[\.\.\.truncated 1 file\.\.\.\]/); + assert.equal(extractPromptDiff(request.messages[1].content), extractShownDiff(stdout)); +}); + test('suggest --stream prints incremental SSE output', async (t) => { const root = await mkdtemp(join(tmpdir(), 'commit-echo-e2e-stream-')); const { home, repo, configDir } = await setupRepo(root); @@ -467,10 +744,12 @@ test('suggest --stream prints incremental SSE output', async (t) => { } res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - model: parsed.model, - choices: [{ message: { content: '1. feat: fallback suggestion' } }], - })); + res.end( + JSON.stringify({ + model: parsed.model, + choices: [{ message: { content: '1. feat: fallback suggestion' } }], + }), + ); return; } @@ -485,30 +764,31 @@ test('suggest --stream prints incremental SSE output', async (t) => { await writeFile( join(configDir, 'config.json'), - JSON.stringify({ - provider: '__custom__', - model: 'fixture-model', - baseUrl: `http://127.0.0.1:${port}`, - apiKey: 'test-key', - historySize: 5, - }, null, 2), - 'utf8' + JSON.stringify( + { + provider: '__custom__', + model: 'fixture-model', + baseUrl: `http://127.0.0.1:${port}`, + apiKey: 'test-key', + historySize: 5, + }, + null, + 2, + ), + 'utf8', ); - const { stdout } = await runSuggestUntil( - ['suggest', '--stream'], - { - cwd: repo, - env: { - ...process.env, - HOME: home, - XDG_CONFIG_HOME: join(home, '.config'), - APPDATA: join(home, 'AppData', 'Roaming'), - FORCE_COLOR: '0', - }, - text: 'feat: streamed suggestion', + const { stdout } = await runSuggestUntil(['suggest', '--stream'], { + cwd: repo, + env: { + ...process.env, + HOME: home, + XDG_CONFIG_HOME: join(home, '.config'), + APPDATA: join(home, 'AppData', 'Roaming'), + FORCE_COLOR: '0', }, - ); + text: 'feat: streamed suggestion', + }); assert.match(stdout, /Streaming suggestions/); assert.match(stdout, /feat: streamed suggestion/); @@ -544,10 +824,12 @@ test('suggest --stream prints incremental Anthropic SSE output', async (t) => { } res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - model: parsed.model, - content: [{ type: 'text', text: '1. feat: anthropic fallback suggestion' }], - })); + res.end( + JSON.stringify({ + model: parsed.model, + content: [{ type: 'text', text: '1. feat: anthropic fallback suggestion' }], + }), + ); return; } @@ -562,30 +844,31 @@ test('suggest --stream prints incremental Anthropic SSE output', async (t) => { await writeFile( join(configDir, 'config.json'), - JSON.stringify({ - provider: 'anthropic', - model: 'claude-sonnet-4-20250514', - baseUrl: `http://127.0.0.1:${port}/v1`, - apiKey: 'test-key', - historySize: 5, - }, null, 2), - 'utf8' + JSON.stringify( + { + provider: 'anthropic', + model: 'claude-sonnet-4-20250514', + baseUrl: `http://127.0.0.1:${port}/v1`, + apiKey: 'test-key', + historySize: 5, + }, + null, + 2, + ), + 'utf8', ); - const { stdout } = await runSuggestUntil( - ['suggest', '--stream'], - { - cwd: repo, - env: { - ...process.env, - HOME: home, - XDG_CONFIG_HOME: join(home, '.config'), - APPDATA: join(home, 'AppData', 'Roaming'), - FORCE_COLOR: '0', - }, - text: 'feat: anthropic streamed suggestion', + const { stdout } = await runSuggestUntil(['suggest', '--stream'], { + cwd: repo, + env: { + ...process.env, + HOME: home, + XDG_CONFIG_HOME: join(home, '.config'), + APPDATA: join(home, 'AppData', 'Roaming'), + FORCE_COLOR: '0', }, - ); + text: 'feat: anthropic streamed suggestion', + }); assert.match(stdout, /Streaming suggestions/); assert.match(stdout, /feat: anthropic streamed suggestion/); @@ -614,10 +897,12 @@ test('suggest --stream --yes streams output and auto-commits the first suggestio } res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - model: parsed.model, - choices: [{ message: { content: '1. feat: fallback suggestion' } }], - })); + res.end( + JSON.stringify({ + model: parsed.model, + choices: [{ message: { content: '1. feat: fallback suggestion' } }], + }), + ); return; } @@ -632,14 +917,18 @@ test('suggest --stream --yes streams output and auto-commits the first suggestio await writeFile( join(configDir, 'config.json'), - JSON.stringify({ - provider: '__custom__', - model: 'fixture-model', - baseUrl: `http://127.0.0.1:${port}`, - apiKey: 'test-key', - historySize: 5, - }, null, 2), - 'utf8' + JSON.stringify( + { + provider: '__custom__', + model: 'fixture-model', + baseUrl: `http://127.0.0.1:${port}`, + apiKey: 'test-key', + historySize: 5, + }, + null, + 2, + ), + 'utf8', ); const child = spawn(process.execPath, [join(process.cwd(), 'dist/index.js'), 'suggest', '--stream', '--yes'], { @@ -674,13 +963,17 @@ test('suggest --stream fails fast for unsupported providers', async (t) => { await writeFile( join(configDir, 'config.json'), - JSON.stringify({ - provider: 'cohere', - model: 'command-r', - apiKey: 'test-key', - historySize: 5, - }, null, 2), - 'utf8' + JSON.stringify( + { + provider: 'cohere', + model: 'command-r', + apiKey: 'test-key', + historySize: 5, + }, + null, + 2, + ), + 'utf8', ); t.after(async () => { @@ -738,14 +1031,18 @@ test('suggest --stream reports parse failure for unparseable streamed output', a await writeFile( join(configDir, 'config.json'), - JSON.stringify({ - provider: '__custom__', - model: 'fixture-model', - baseUrl: `http://127.0.0.1:${port}`, - apiKey: 'test-key', - historySize: 5, - }, null, 2), - 'utf8' + JSON.stringify( + { + provider: '__custom__', + model: 'fixture-model', + baseUrl: `http://127.0.0.1:${port}`, + apiKey: 'test-key', + historySize: 5, + }, + null, + 2, + ), + 'utf8', ); const child = spawn(process.execPath, [join(process.cwd(), 'dist/index.js'), 'suggest', '--stream'], {