1- import { Console , Effect } from "effect" ;
1+ import { Console , Effect , Option } from "effect" ;
22import { Command , Flag } from "effect/unstable/cli" ;
33import { loadProjectConfig } from "../config/project" ;
44import { resolveProviderConfig } from "../config/provider" ;
@@ -7,9 +7,8 @@ import { emptyProjectConfig } from "../domain/project";
77import { ConfigError } from "../shared/errors" ;
88import { printCommitResult , printDryRunResult } from "../shared/output" ;
99import { parseCsvValues } from "../shared/text" ;
10- import { withProgressSpan } from "../shared/tracing" ;
1110import { runCommitService } from "../services/commit-service" ;
12- import { detectVcs , getVcsClient } from "../services/vcs" ;
11+ import { Vcs } from "../services/vcs" ;
1312import {
1413 apiKeyFlag ,
1514 baseUrlFlag ,
@@ -20,106 +19,162 @@ import {
2019 vcsFlag ,
2120} from "./shared" ;
2221
23- const parseTrailers = (
22+ const parseTrailers = Effect . fn ( function * (
2423 coAuthors : ReadonlyArray < string > ,
2524 trailerValues : ReadonlyArray < string > ,
2625 includeAttribution : boolean ,
2726 ignoreCoAuthor : boolean ,
28- ) : Effect . Effect < Array < Trailer > , ConfigError > =>
29- Effect . gen ( function * ( ) {
30- const trailers : Array < Trailer > = [ ] ;
31- if ( ! ignoreCoAuthor ) {
32- for ( const value of parseCsvValues ( coAuthors ) ) {
33- trailers . push ( {
34- key : "Co-Authored-By" ,
35- value,
36- } ) ;
37- }
38- }
39- for ( const value of parseCsvValues ( trailerValues ) ) {
40- const trailer = parseTrailerText ( value ) ;
41- if ( trailer == null ) {
42- return yield * Effect . fail (
43- new ConfigError ( {
44- message : `invalid --trailer format "${ value } ": expected "Key: Value"` ,
45- } ) ,
46- ) ;
47- }
48- trailers . push ( {
49- key : trailer . key ,
50- value : trailer . value ,
51- } ) ;
52- }
53- if ( includeAttribution ) {
27+ ) {
28+ const trailers : Array < Trailer > = [ ] ;
29+ if ( ! ignoreCoAuthor ) {
30+ for ( const value of parseCsvValues ( coAuthors ) ) {
5431 trailers . push ( {
5532 key : "Co-Authored-By" ,
56- value : "Git Agent <noreply@git-agent.dev>" ,
33+ value,
5734 } ) ;
5835 }
59- return trailers ;
60- } ) ;
61-
62- const runCommitCommand = Effect . fn (
63- function * ( input ) {
64- if ( input . amend && input . noStage ) {
65- return yield * Effect . fail (
66- new ConfigError ( { message : "--amend and --no-stage cannot be used together" } ) ,
67- ) ;
36+ }
37+ for ( const value of parseCsvValues ( trailerValues ) ) {
38+ const trailer = parseTrailerText ( value ) ;
39+ if ( trailer == null ) {
40+ return yield * new ConfigError ( {
41+ message : `invalid --trailer format "${ value } ": expected "Key: Value"` ,
42+ } ) ;
6843 }
69-
70- const vcsKind = yield * detectVcs ( input . cwd , toOptionalString ( input . vcs ) ) ;
71- yield * Effect . annotateCurrentSpan ( {
72- vcs : vcsKind ,
44+ trailers . push ( {
45+ key : trailer . key ,
46+ value : trailer . value ,
7347 } ) ;
74- const vcs = getVcsClient ( vcsKind ) ;
75- const provider = yield * resolveProviderConfig ( {
76- cwd : input . cwd ,
77- vcs : vcsKind ,
78- apiKey : toOptionalString ( input . apiKey ) ,
79- baseUrl : toOptionalString ( input . baseUrl ) ,
80- model : toOptionalString ( input . model ) ,
81- free : input . free ,
48+ }
49+ if ( includeAttribution ) {
50+ trailers . push ( {
51+ key : "Co-Authored-By" ,
52+ value : "Git Agent <noreply@git-agent.dev>" ,
8253 } ) ;
54+ }
55+ return trailers ;
56+ } ) ;
8357
84- if ( provider . apiKey . length === 0 ) {
85- return yield * Effect . fail (
86- new ConfigError ( {
87- message :
88- "error: no API key configured\nhint: set --api-key, add api_key to ~/.config/git-agent/config.yml, or use build-time embedded credentials" ,
58+ interface CommitCommandInput {
59+ readonly cwd : string ;
60+ readonly vcs : Option . Option < string > ;
61+ readonly apiKey : Option . Option < string > ;
62+ readonly baseUrl : Option . Option < string > ;
63+ readonly model : Option . Option < string > ;
64+ readonly free : boolean ;
65+ readonly intent : Option . Option < string > ;
66+ readonly dryRun : boolean ;
67+ readonly noStage : boolean ;
68+ readonly amend : boolean ;
69+ readonly maxDiffLines : number ;
70+ readonly noAttribution : boolean ;
71+ readonly coAuthor : ReadonlyArray < string > ;
72+ readonly trailer : ReadonlyArray < string > ;
73+ }
74+
75+ const runCommitCommand = ( input : CommitCommandInput ) => {
76+ const requestedVcs = toOptionalString ( input . vcs ) ?? "auto" ;
77+ return Effect . withSpan (
78+ Effect . gen ( function * ( ) {
79+ yield * Effect . annotateCurrentSpan ( {
80+ amend : input . amend ,
81+ dry_run : input . dryRun ,
82+ no_stage : input . noStage ,
83+ requested_vcs : requestedVcs ,
84+ } ) ;
85+ if ( input . amend && input . noStage ) {
86+ return yield * new ConfigError ( {
87+ message : "--amend and --no-stage cannot be used together" ,
88+ } ) ;
89+ }
90+
91+ const vcsService = yield * Vcs ;
92+ const { kind : vcsKind , client : vcs } = yield * vcsService . resolve (
93+ input . cwd ,
94+ toOptionalString ( input . vcs ) ,
95+ ) ;
96+ yield * Effect . annotateCurrentSpan ( {
97+ vcs : vcsKind ,
98+ } ) ;
99+ const provider = yield * Effect . withSpan (
100+ resolveProviderConfig ( {
101+ cwd : input . cwd ,
102+ vcs : vcsKind ,
103+ apiKey : toOptionalString ( input . apiKey ) ,
104+ baseUrl : toOptionalString ( input . baseUrl ) ,
105+ model : toOptionalString ( input . model ) ,
106+ free : input . free ,
89107 } ) ,
108+ "commit.resolve-provider" ,
109+ {
110+ attributes : {
111+ requested_vcs : requestedVcs ,
112+ vcs : vcsKind ,
113+ free : input . free ,
114+ } ,
115+ captureStackTrace : false ,
116+ } ,
90117 ) ;
91- }
92118
93- const repoRoot = yield * vcs . repoRoot ( input . cwd ) ;
94- const projectConfig = ( yield * loadProjectConfig ( repoRoot ) ) ?? emptyProjectConfig ( ) ;
95- const trailers = yield * parseTrailers (
96- input . coAuthor ,
97- input . trailer ,
98- ! ( provider . noGitAgentCoAuthor || projectConfig . noGitAgentCoAuthor || input . noAttribution ) ,
99- provider . noModelCoAuthor || projectConfig . noModelCoAuthor ,
100- ) ;
119+ if ( provider . apiKey . length === 0 ) {
120+ return yield * new ConfigError ( {
121+ message :
122+ "error: no API key configured\nhint: set --api-key, add api_key to ~/.config/git-agent/config.yml, or use build-time embedded credentials" ,
123+ } ) ;
124+ }
101125
102- return yield * runCommitService ( {
103- cwd : repoRoot ,
104- provider,
105- vcs,
106- projectConfig,
107- intent : toOptionalString ( input . intent ) ,
108- trailers,
109- dryRun : input . dryRun ,
110- noStage : input . noStage ,
111- amend : input . amend ,
112- maxDiffLines : input . maxDiffLines > 0 ? input . maxDiffLines : projectConfig . maxDiffLines ,
113- } ) ;
114- } ,
115- ( effect , input ) =>
116- withProgressSpan ( effect , "commit.prepare-request" , {
117- amend : input . amend ,
118- dry_run : input . dryRun ,
119- no_stage : input . noStage ,
120- requested_vcs : toOptionalString ( input . vcs ) ?? "auto" ,
126+ const repoRoot = yield * vcs . repoRoot ( input . cwd ) ;
127+ const projectConfig =
128+ ( yield * Effect . withSpan ( loadProjectConfig ( repoRoot ) , "commit.load-project-config" , {
129+ attributes : {
130+ vcs : vcsKind ,
131+ } ,
132+ captureStackTrace : false ,
133+ } ) ) ?? emptyProjectConfig ( ) ;
134+ const trailers = yield * parseTrailers (
135+ input . coAuthor ,
136+ input . trailer ,
137+ ! ( provider . noGitAgentCoAuthor || projectConfig . noGitAgentCoAuthor || input . noAttribution ) ,
138+ provider . noModelCoAuthor || projectConfig . noModelCoAuthor ,
139+ ) ;
140+
141+ return yield * Effect . withSpan (
142+ runCommitService ( {
143+ cwd : repoRoot ,
144+ provider,
145+ vcs,
146+ projectConfig,
147+ intent : toOptionalString ( input . intent ) ,
148+ trailers,
149+ dryRun : input . dryRun ,
150+ noStage : input . noStage ,
151+ amend : input . amend ,
152+ maxDiffLines : input . maxDiffLines > 0 ? input . maxDiffLines : projectConfig . maxDiffLines ,
153+ } ) ,
154+ "commit.run" ,
155+ {
156+ attributes : {
157+ vcs : vcsKind ,
158+ dry_run : input . dryRun ,
159+ no_stage : input . noStage ,
160+ amend : input . amend ,
161+ } ,
162+ captureStackTrace : false ,
163+ } ,
164+ ) ;
121165 } ) ,
122- ) ;
166+ "commit.prepare-request" ,
167+ {
168+ attributes : {
169+ amend : input . amend ,
170+ dry_run : input . dryRun ,
171+ no_stage : input . noStage ,
172+ requested_vcs : requestedVcs ,
173+ } ,
174+ captureStackTrace : false ,
175+ } ,
176+ ) ;
177+ } ;
123178
124179export const commandCommit = Command . make (
125180 "commit" ,
0 commit comments