@@ -14,6 +14,7 @@ import type { CustomToolDefinition } from './custom-tool'
1414import type { AgentDefinition } from '@codebuff/common/templates/initial-agents-dir/types/agent-definition'
1515import type { Logger } from '@codebuff/common/types/contracts/logger'
1616import type { CodebuffFileSystem } from '@codebuff/common/types/filesystem'
17+ import type { CodebuffSpawn } from '@codebuff/common/types/spawn'
1718import type { Message } from '@codebuff/common/types/messages/codebuff-message'
1819import type {
1920 AgentOutput ,
@@ -38,6 +39,7 @@ export type InitialSessionStateOptions = {
3839 customToolDefinitions ?: CustomToolDefinition [ ]
3940 maxAgentSteps ?: number
4041 fs ?: CodebuffFileSystem
42+ spawn ?: CodebuffSpawn
4143 logger ?: Logger
4244}
4345
@@ -119,6 +121,99 @@ async function computeProjectIndex(
119121 return { fileTree, fileTokenScores, tokenCallers }
120122}
121123
124+ /**
125+ * Helper to convert ChildProcess to Promise with stdout/stderr
126+ */
127+ function childProcessToPromise (
128+ proc : ReturnType < CodebuffSpawn > ,
129+ ) : Promise < { stdout : string ; stderr : string } > {
130+ return new Promise ( ( resolve , reject ) => {
131+ let stdout = ''
132+ let stderr = ''
133+
134+ proc . stdout ?. on ( 'data' , ( data : Buffer ) => {
135+ stdout += data . toString ( )
136+ } )
137+
138+ proc . stderr ?. on ( 'data' , ( data : Buffer ) => {
139+ stderr += data . toString ( )
140+ } )
141+
142+ proc . on ( 'close' , ( code : number | null ) => {
143+ if ( code === 0 ) {
144+ resolve ( { stdout, stderr } )
145+ } else {
146+ reject ( new Error ( `Command exited with code ${ code } ` ) )
147+ }
148+ } )
149+
150+ proc . on ( 'error' , reject )
151+ } )
152+ }
153+
154+ /**
155+ * Retrieves git changes for the project using the provided spawn function
156+ */
157+ async function getGitChanges ( params : {
158+ cwd : string
159+ spawn : CodebuffSpawn
160+ logger : Logger
161+ } ) : Promise < {
162+ status : string
163+ diff : string
164+ diffCached : string
165+ lastCommitMessages : string
166+ } > {
167+ const { cwd, spawn, logger } = params
168+
169+ const status = childProcessToPromise ( spawn ( 'git' , [ 'status' ] , { cwd } ) )
170+ . then ( ( { stdout } ) => stdout )
171+ . catch ( ( error ) => {
172+ logger . debug ?.( { error } , 'Failed to get git status' )
173+ return ''
174+ } )
175+
176+ const diff = childProcessToPromise ( spawn ( 'git' , [ 'diff' ] , { cwd } ) )
177+ . then ( ( { stdout } ) => stdout )
178+ . catch ( ( error ) => {
179+ logger . debug ?.( { error } , 'Failed to get git diff' )
180+ return ''
181+ } )
182+
183+ const diffCached = childProcessToPromise (
184+ spawn ( 'git' , [ 'diff' , '--cached' ] , { cwd } ) ,
185+ )
186+ . then ( ( { stdout } ) => stdout )
187+ . catch ( ( error ) => {
188+ logger . debug ?.( { error } , 'Failed to get git diff --cached' )
189+ return ''
190+ } )
191+
192+ const lastCommitMessages = childProcessToPromise (
193+ spawn ( 'git' , [ 'shortlog' , 'HEAD~10..HEAD' ] , { cwd } ) ,
194+ )
195+ . then ( ( { stdout } ) =>
196+ stdout
197+ . trim ( )
198+ . split ( '\n' )
199+ . slice ( 1 )
200+ . reverse ( )
201+ . map ( ( line ) => line . trim ( ) )
202+ . join ( '\n' ) ,
203+ )
204+ . catch ( ( error ) => {
205+ logger . debug ?.( { error } , 'Failed to get lastCommitMessages' )
206+ return ''
207+ } )
208+
209+ return {
210+ status : await status ,
211+ diff : await diff ,
212+ diffCached : await diffCached ,
213+ lastCommitMessages : await lastCommitMessages ,
214+ }
215+ }
216+
122217/**
123218 * Discovers project files using .gitignore patterns when projectFiles is undefined
124219 */
@@ -231,6 +326,7 @@ export async function initialSessionState(
231326 projectFiles,
232327 knowledgeFiles,
233328 fs,
329+ spawn,
234330 logger,
235331 } = params
236332 if ( ! agentDefinitions ) {
@@ -242,6 +338,10 @@ export async function initialSessionState(
242338 if ( ! fs ) {
243339 fs = ( require ( 'fs' ) as typeof fsType ) . promises
244340 }
341+ if ( ! spawn ) {
342+ const { spawn : nodeSpawn } = require ( 'child_process' )
343+ spawn = nodeSpawn as CodebuffSpawn
344+ }
245345 if ( ! logger ) {
246346 logger = {
247347 debug : ( ) => { } ,
@@ -276,6 +376,16 @@ export async function initialSessionState(
276376 tokenCallers = result . tokenCallers
277377 }
278378
379+ // Gather git changes if cwd is available
380+ const gitChanges = cwd
381+ ? await getGitChanges ( { cwd, spawn, logger } )
382+ : {
383+ status : '' ,
384+ diff : '' ,
385+ diffCached : '' ,
386+ lastCommitMessages : '' ,
387+ }
388+
279389 const initialState = getInitialSessionState ( {
280390 projectRoot : cwd ?? process . cwd ( ) ,
281391 cwd : cwd ?? process . cwd ( ) ,
@@ -286,12 +396,7 @@ export async function initialSessionState(
286396 userKnowledgeFiles : { } ,
287397 agentTemplates : processedAgentTemplates ,
288398 customToolDefinitions : processedCustomToolDefinitions ,
289- gitChanges : {
290- status : '' ,
291- diff : '' ,
292- diffCached : '' ,
293- lastCommitMessages : '' ,
294- } ,
399+ gitChanges,
295400 changesSinceLastChat : { } ,
296401 shellConfigFiles : { } ,
297402 systemInfo : {
0 commit comments