@@ -33,10 +33,11 @@ import (
3333var version = "dev"
3434
3535type Config struct {
36- Provider string `yaml:"provider"`
37- ApiKey string `yaml:"api_key"`
38- Model string `yaml:"model"`
39- BinPath string `yaml:"bin_path"` // <-- [수정 1] 이 줄을 추가하세요
36+ Provider string `yaml:"provider"`
37+ ApiKey string `yaml:"api_key"`
38+ Model string `yaml:"model"`
39+ BinPath string `yaml:"bin_path"`
40+ Args []string `yaml:"args"`
4041}
4142
4243type TargetInfo struct {
@@ -71,6 +72,7 @@ type options struct {
7172
7273type commandLLM struct {
7374 binPath string
75+ args []string // <-- [수정 2] 템플릿 저장용 필드 추가
7476}
7577
7678func main () {
@@ -137,8 +139,6 @@ func main() {
137139 clear (backups )
138140
139141 if err != nil {
140- // 에러가 났으므로 즉시 소스를 원래대로 돌려놓고 종료합니다.
141- // (log.Fatalf는 os.Exit을 호출하므로 defer가 실행되지 않을 수 있어 수동 복구합니다)
142142 revertCode (backups )
143143 log .Fatalf ("\n [lx] Stop: Execution failed. Fix your Go code first.\n Error: %v" , err )
144144 }
@@ -208,12 +208,9 @@ func injectSpyCode(root string) (map[string]fileBackup, error) {
208208 return true
209209 }
210210
211- // 1. 함수의 반환 타입들을 순서대로 추출합니다.
212- // 예: func foo() (int, error) -> [int, error]
213211 var returnTypes []ast.Expr
214212 if fn .Type .Results != nil {
215213 for _ , field := range fn .Type .Results .List {
216- // 이름이 있는 반환값 (a, b int) 또는 없는 반환값 (int, int) 처리
217214 count := len (field .Names )
218215 if count == 0 {
219216 count = 1
@@ -227,7 +224,6 @@ func injectSpyCode(root string) (map[string]fileBackup, error) {
227224 isVoid := len (returnTypes ) == 0
228225
229226 if isVoid {
230- // [Void 함수 처리] SpyVoid 사용 (기존과 동일)
231227 deferStmt := & ast.DeferStmt {
232228 Call : & ast.CallExpr {
233229 Fun : & ast.SelectorExpr {
@@ -245,38 +241,29 @@ func injectSpyCode(root string) (map[string]fileBackup, error) {
245241 fn .Body .List = append ([]ast.Stmt {deferStmt }, fn .Body .List ... )
246242 modified = true
247243 } else {
248- // [반환값이 있는 함수 처리]
249244 ast .Inspect (fn .Body , func (inner ast.Node ) bool {
250245 retStmt , ok := inner .(* ast.ReturnStmt )
251246 if ! ok {
252247 return true
253248 }
254249
255250 for i , resultExpr := range retStmt .Results {
256- // 인덱스 안전장치 및 이미 Spy 처리된 것 스킵
257251 if i >= len (returnTypes ) || isSpyCall (resultExpr ) {
258252 continue
259253 }
260254
261- // [핵심 수정] 제네릭 타입을 명시적으로 생성합니다.
262- // 예: lx.Spy(...) -> lx.Spy[float64](...)
263-
264- // 1. lx.Spy 선택자 생성
265255 spySelector := & ast.SelectorExpr {
266256 X : ast .NewIdent ("lx" ),
267257 Sel : ast .NewIdent ("Spy" ),
268258 }
269259
270- // 2. 제네릭 인스턴스화 표현식 생성 (IndexExpr 사용)
271- // lx.Spy[Type] 형태를 만듭니다.
272260 spyInstance := & ast.IndexExpr {
273261 X : spySelector ,
274- Index : returnTypes [i ], // 미리 추출해둔 타입 사용
262+ Index : returnTypes [i ],
275263 }
276264
277- // 3. 함수 호출 표현식 생성
278265 spyCall := & ast.CallExpr {
279- Fun : spyInstance , // 명시된 제네릭 함수 사용
266+ Fun : spyInstance ,
280267 Args : []ast.Expr {
281268 & ast.BasicLit {
282269 Kind : token .STRING ,
@@ -360,7 +347,6 @@ func runAndCapture(opts options, rootDir string) ([]TraceData, error) {
360347 return nil , err
361348 }
362349
363- // 1. 프로젝트 내의 실행 가능한 모든 진입점(package main이 있는 디렉토리)을 찾습니다.
364350 entryPoints , err := findMainPackages (absRoot )
365351 if err != nil {
366352 return nil , fmt .Errorf ("failed to scan for main packages: %w" , err )
@@ -378,9 +364,8 @@ func runAndCapture(opts options, rootDir string) ([]TraceData, error) {
378364 var allTraces []TraceData
379365 var executionErrors []string
380366
381- // 2. 발견된 모든 진입점에 대해 각각 실행합니다.
382367 for _ , dir := range entryPoints {
383- // 상대 경로로 변환하여 로그 가독성 향상
368+
384369 relDir , _ := filepath .Rel (absRoot , dir )
385370 if relDir == "" {
386371 relDir = "."
@@ -389,8 +374,7 @@ func runAndCapture(opts options, rootDir string) ([]TraceData, error) {
389374
390375 traces , err := executeSinglePackage (ctx , goExe , dir , opts )
391376 if err != nil {
392- // 특정 패키지 실행 실패가 전체 프로세스를 중단시키지 않도록 경고만 하고 넘어갈지 결정해야 합니다.
393- // 여기서는 에러를 기록하고 계속 진행합니다.
377+
394378 executionErrors = append (executionErrors , fmt .Sprintf ("%s: %v" , relDir , err ))
395379 continue
396380 }
@@ -399,7 +383,7 @@ func runAndCapture(opts options, rootDir string) ([]TraceData, error) {
399383
400384 if len (executionErrors ) > 0 {
401385 errMsg := strings .Join (executionErrors , "\n \t - " )
402- // 경고(Warning)가 아니라 에러(Error)를 반환하여 main에서 잡게 합니다.
386+
403387 return allTraces , fmt .Errorf ("execution failed in:\n \t - %s" , errMsg )
404388 }
405389
@@ -450,7 +434,7 @@ func executeSinglePackage(ctx context.Context, goExe, dir string, opts options)
450434 var td TraceData
451435 if err := json .Unmarshal ([]byte (payload ), & td ); err == nil {
452436 td .Function = normalizeFuncName (td .Function )
453- // 파일 경로는 절대 경로로 저장되어야 나중에 매칭하기 쉽습니다.
437+
454438 if ! filepath .IsAbs (td .File ) {
455439 td .File = filepath .Join (dir , td .File )
456440 }
@@ -473,7 +457,6 @@ func executeSinglePackage(ctx context.Context, goExe, dir string, opts options)
473457 waitErr = scanErr
474458 }
475459
476- // 컨텍스트 타임아웃 체크
477460 if ctx .Err () == context .DeadlineExceeded {
478461 return traces , fmt .Errorf ("timeout" )
479462 }
@@ -503,16 +486,15 @@ func findMainPackages(root string) ([]string, error) {
503486
504487 dir := filepath .Dir (path )
505488 if _ , ok := seen [dir ]; ok {
506- // 이미 이 디렉토리는 package main임을 확인했으므로 스킵
489+
507490 return nil
508491 }
509492
510- // 파일 내용을 파싱하여 패키지 이름 확인
511493 fset := token .NewFileSet ()
512- // PackageClauseOnly 옵션으로 빠르게 패키지명만 파싱
494+
513495 f , err := parser .ParseFile (fset , path , nil , parser .ParseComments | parser .PackageClauseOnly )
514496 if err != nil {
515- return nil // 파싱 에러 무시
497+ return nil
516498 }
517499
518500 if f .Name .Name == "main" {
@@ -768,7 +750,6 @@ func processSingleTarget(opts options, llm LLM, cfg *Config, target TargetInfo)
768750 }
769751 }
770752
771- // 4. 시스템 프롬프트 강화 (규칙 추가)
772753 systemPrompt := fmt .Sprintf (`GO FUNC BODY GEN.
773754
774755SIG: %s
@@ -862,7 +843,6 @@ func newLLM(cfg *Config) (LLM, error) {
862843 return nil , errors .New ("nil config" )
863844 }
864845
865- // Model은 공통 필수
866846 if strings .TrimSpace (cfg .Model ) == "" {
867847 return nil , errors .New ("empty model" )
868848 }
@@ -874,7 +854,6 @@ func newLLM(cfg *Config) (LLM, error) {
874854
875855 switch provider {
876856 case "gemini" :
877- // 기존 구글 API 방식
878857 if strings .TrimSpace (cfg .ApiKey ) == "" {
879858 return nil , errors .New ("empty api_key" )
880859 }
@@ -887,12 +866,15 @@ func newLLM(cfg *Config) (LLM, error) {
887866 }
888867 return & geminiLLM {client : client }, nil
889868
890- case "command" , "gemini-cli" :
891- // [수정 2] 새로운 커맨드 방식 추가
869+ case "command" :
892870 if strings .TrimSpace (cfg .BinPath ) == "" {
893871 return nil , errors .New ("empty bin_path (required for command provider)" )
894872 }
895- return & commandLLM {binPath : cfg .BinPath }, nil
873+
874+ return & commandLLM {
875+ binPath : cfg .BinPath ,
876+ args : cfg .Args ,
877+ }, nil
896878
897879 default :
898880 return nil , fmt .Errorf ("unsupported provider: %s" , cfg .Provider )
@@ -908,17 +890,27 @@ func (g *geminiLLM) Generate(ctx context.Context, model string, prompt string) (
908890}
909891
910892func (c * commandLLM ) Generate (ctx context.Context , model string , prompt string ) (string , error ) {
911- args := []string {"-p" , prompt , "-m" , model , "-o" , "text" }
893+ var finalArgs []string
894+
895+ if len (c .args ) == 0 {
896+ finalArgs = []string {"-p" , prompt , "-m" , model , "-o" , "text" }
897+ } else {
898+ for _ , arg := range c .args {
899+ replaced := strings .ReplaceAll (arg , "{{prompt}}" , prompt )
900+ replaced = strings .ReplaceAll (replaced , "{{model}}" , model )
901+ finalArgs = append (finalArgs , replaced )
902+ }
903+ }
912904
913- cmd := exec .CommandContext (ctx , c .binPath , args ... )
905+ cmd := exec .CommandContext (ctx , c .binPath , finalArgs ... )
914906
915907 var out bytes.Buffer
916908 var stderr bytes.Buffer
917909 cmd .Stdout = & out
918910 cmd .Stderr = & stderr
919911
920912 if err := cmd .Run (); err != nil {
921- return "" , fmt .Errorf ("gemini-cli failed: %v\n Stderr: %s" , err , stderr .String ())
913+ return "" , fmt .Errorf ("command execution failed: %v\n Stderr: %s" , err , stderr .String ())
922914 }
923915
924916 return out .String (), nil
@@ -991,7 +983,6 @@ func loadConfig() (*Config, string, error) {
991983}
992984
993985func cleanAICode (code string ) string {
994- // 1. 마크다운(```) 제거
995986 if start := strings .Index (code , "```" ); start != - 1 {
996987 if firstNL := strings .Index (code [start :], "\n " ); firstNL != - 1 {
997988 content := code [start + firstNL + 1 :]
@@ -1001,7 +992,6 @@ func cleanAICode(code string) string {
1001992 }
1002993 }
1003994
1004- // 2. 함수 시그니처(func ... { ) 제거
1005995 if strings .Contains (code , "func " ) && strings .Contains (code , "{" ) {
1006996 if open := strings .Index (code , "{" ); open != - 1 {
1007997 if close := strings .LastIndex (code , "}" ); close != - 1 {
@@ -1010,26 +1000,18 @@ func cleanAICode(code string) string {
10101000 }
10111001 }
10121002
1013- // [핵심 수정] 3. 덩그러니 남은 외곽 중괄호({ ... }) 제거
1014- // AI가 코드 블록만 덜렁 줄 때가 있는데, 이걸 벗겨야 이중 중괄호가 안 생깁니다.
10151003 trimmed := strings .TrimSpace (code )
10161004 if strings .HasPrefix (trimmed , "{" ) && strings .HasSuffix (trimmed , "}" ) {
1017- // 앞뒤 중괄호를 제거합니다.
10181005 code = trimmed [1 : len (trimmed )- 1 ]
10191006 }
10201007
1021- // 4. 줄 단위로 쪼개서 불필요한 공백 및 lx.Gen 호출 제거
10221008 lines := strings .Split (code , "\n " )
10231009 var finalLines []string
10241010 for _ , line := range lines {
10251011 t := strings .TrimSpace (line )
1026- // 빈 줄이거나 lx.Gen 호출이면 무시
10271012 if t == "" || strings .Contains (t , "lx.Gen(" ) {
10281013 continue
10291014 }
1030- // 원본 라인의 들여쓰기는 유지하는 것이 좋지만,
1031- // 외곽 중괄호를 벗긴 경우 들여쓰기가 과도할 수 있으므로 TrimSpace를 하거나
1032- // 여기서는 안전하게 내용만 가져갑니다. gofmt가 어차피 정리해줍니다.
10331015 finalLines = append (finalLines , line )
10341016 }
10351017
0 commit comments