@@ -29,7 +29,16 @@ const TOOL_OUTPUT_DIR = "tool-output";
2929
3030const log = getLogger ( [ LOG_NAMESPACE_ROOT , "session" , "context-store" ] ) ;
3131
32- const AUTHOR = {
32+ export type CheckpointAuthor = {
33+ name : string ;
34+ email : string ;
35+ } ;
36+
37+ // Cycle commits shell out to system git, so operator commit-author hooks see
38+ // this identity. Prefer their global git user when both name and email are
39+ // set; otherwise keep Interchange's harness fallback so machines without a
40+ // git identity still checkpoint.
41+ const HARNESS_AUTHOR : CheckpointAuthor = {
3342 name : "interchange-harness" ,
3443 email : "harness@interchange.local" ,
3544} ;
@@ -299,16 +308,20 @@ export async function loadRecentTurns(dir: string, minTurns: number): Promise<Co
299308 return turns ;
300309}
301310
302- async function runGit ( dir : string , args : string [ ] ) : Promise < string > {
311+ async function runGit ( dir : string , args : string [ ] , author ?: CheckpointAuthor ) : Promise < string > {
303312 const proc = Bun . spawn ( [ "git" , "-C" , dir , ...args ] , {
304313 stdout : "pipe" ,
305314 stderr : "pipe" ,
306315 env : {
307316 ...process . env ,
308- GIT_AUTHOR_NAME : AUTHOR . name ,
309- GIT_AUTHOR_EMAIL : AUTHOR . email ,
310- GIT_COMMITTER_NAME : AUTHOR . name ,
311- GIT_COMMITTER_EMAIL : AUTHOR . email ,
317+ ...( author === undefined
318+ ? { }
319+ : {
320+ GIT_AUTHOR_NAME : author . name ,
321+ GIT_AUTHOR_EMAIL : author . email ,
322+ GIT_COMMITTER_NAME : author . name ,
323+ GIT_COMMITTER_EMAIL : author . email ,
324+ } ) ,
312325 } ,
313326 } ) ;
314327 const [ exitCode , stdout , stderr ] = await Promise . all ( [
@@ -322,6 +335,34 @@ async function runGit(dir: string, args: string[]): Promise<string> {
322335 return stdout . trimEnd ( ) ;
323336}
324337
338+ async function gitConfigGlobal ( key : string , env : NodeJS . ProcessEnv ) : Promise < string | null > {
339+ const proc = Bun . spawn ( [ "git" , "config" , "--global" , "--get" , key ] , {
340+ stdout : "pipe" ,
341+ stderr : "pipe" ,
342+ env,
343+ } ) ;
344+ const [ exitCode , stdout ] = await Promise . all ( [ proc . exited , new Response ( proc . stdout ) . text ( ) ] ) ;
345+ if ( exitCode !== 0 ) return null ;
346+ const value = stdout . trim ( ) ;
347+ return value . length > 0 ? value : null ;
348+ }
349+
350+ /**
351+ * Author for Corbits cycle commits. Uses the operator's global git identity
352+ * when both `user.name` and `user.email` are set; otherwise the Interchange
353+ * harness identity.
354+ */
355+ export async function resolveCheckpointAuthor (
356+ env : NodeJS . ProcessEnv = process . env ,
357+ ) : Promise < CheckpointAuthor > {
358+ const [ name , email ] = await Promise . all ( [
359+ gitConfigGlobal ( "user.name" , env ) ,
360+ gitConfigGlobal ( "user.email" , env ) ,
361+ ] ) ;
362+ if ( name === null || email === null ) return HARNESS_AUTHOR ;
363+ return { name, email } ;
364+ }
365+
325366/**
326367 * Names of the tail turn segments (`turns-0001.jsonl`, ...) present in a commit
327368 * tree, in segment order. The base store reads the zeroth segment itself; these
@@ -395,7 +436,11 @@ async function reconcileSegmentStaging(
395436 * segment files so `git add` re-hashes only the small active segment, and only
396437 * spilled tool-output blobs that are new since the last commit are staged.
397438 */
398- export async function createOptimizedContextStore ( dir : string ) : Promise < ContextStore > {
439+ export async function createOptimizedContextStore (
440+ dir : string ,
441+ opts ?: { author ?: CheckpointAuthor } ,
442+ ) : Promise < ContextStore > {
443+ const author = opts ?. author ?? ( await resolveCheckpointAuthor ( ) ) ;
399444 const base = await createIsogitStore ( dir ) ;
400445 const pendingBlobFilepaths = new Set < string > ( ) ;
401446 const pendingSegmentPaths = new Set < string > ( ) ;
@@ -550,12 +595,11 @@ export async function createOptimizedContextStore(dir: string): Promise<ContextS
550595 if ( remove . length > 0 ) {
551596 await runGit ( dir , [ "rm" , "--cached" , "--ignore-unmatch" , "--" , ...remove ] ) ;
552597 }
553- await runGit ( dir , [
554- "commit" ,
555- "-m" ,
556- options . message ,
557- `--author=${ AUTHOR . name } <${ AUTHOR . email } >` ,
558- ] ) ;
598+ await runGit (
599+ dir ,
600+ [ "commit" , "-m" , options . message , `--author=${ author . name } <${ author . email } >` ] ,
601+ author ,
602+ ) ;
559603 pendingBlobFilepaths . clear ( ) ;
560604 pendingSegmentPaths . clear ( ) ;
561605 return describeHead ( dir , options . message ) ;
0 commit comments