mirror of
https://github.com/cexll/myclaude.git
synced 2026-02-04 02:20:42 +08:00
## Overview Complete implementation of enterprise-level workflow features including multi-backend execution (Codex/Claude/Gemini), GitHub issue-to-PR automation, hooks system, and comprehensive documentation. ## Major Changes ### 1. Multi-Backend Support (codeagent-wrapper) - Renamed codex-wrapper → codeagent-wrapper - Backend interface with Codex/Claude/Gemini implementations - Multi-format JSON stream parser (auto-detects backend) - CLI flag: --backend codex|claude|gemini (default: codex) - Test coverage: 89.2% **Files:** - codeagent-wrapper/backend.go - Backend interface - codeagent-wrapper/parser.go - Multi-format parser - codeagent-wrapper/config.go - CLI parsing with backend selection - codeagent-wrapper/executor.go - Process execution - codeagent-wrapper/logger.go - Async logging - codeagent-wrapper/utils.go - Utilities ### 2. GitHub Workflow Commands - /gh-create-issue - Create structured issues via guided dialogue - /gh-implement - Issue-to-PR automation with full dev lifecycle **Files:** - github-workflow/commands/gh-create-issue.md - github-workflow/commands/gh-implement.md - skills/codeagent/SKILL.md ### 3. Hooks System - UserPromptSubmit hook for skill activation - Pre-commit example with code quality checks - merge_json operation in install.py for settings.json merging **Files:** - hooks/skill-activation-prompt.sh|.js - hooks/pre-commit.sh - hooks/hooks-config.json - hooks/test-skill-activation.sh ### 4. Skills System - skill-rules.json for auto-activation - codeagent skill for multi-backend wrapper **Files:** - skills/skill-rules.json - skills/codeagent/SKILL.md - skills/codex/SKILL.md (updated) ### 5. Installation System - install.py: Added merge_json operation - config.json: Added "gh" module - config.schema.json: Added op_merge_json schema ### 6. CI/CD - GitHub Actions workflow for testing and building **Files:** - .github/workflows/ci.yml ### 7. Comprehensive Documentation - Architecture overview with ASCII diagrams - Codeagent-wrapper complete usage guide - GitHub workflow detailed examples - Hooks customization guide **Files:** - docs/architecture.md (21KB) - docs/CODEAGENT-WRAPPER.md (9KB) - docs/GITHUB-WORKFLOW.md (9KB) - docs/HOOKS.md (4KB) - docs/enterprise-workflow-ideas.md - README.md (updated with doc links) ## Test Results - All tests passing ✅ - Coverage: 89.2% - Security scan: 0 issues (gosec) ## Breaking Changes - codex-wrapper renamed to codeagent-wrapper - Default backend: codex (documented in README) ## Migration Guide Users with codex-wrapper installed should: 1. Run: python3 install.py --module dev --force 2. Update shell aliases if any 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
244 lines
5.2 KiB
Go
244 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
// Logger writes log messages asynchronously to a temp file.
|
|
// It is intentionally minimal: a buffered channel + single worker goroutine
|
|
// to avoid contention while keeping ordering guarantees.
|
|
type Logger struct {
|
|
path string
|
|
file *os.File
|
|
writer *bufio.Writer
|
|
ch chan logEntry
|
|
flushReq chan chan struct{}
|
|
done chan struct{}
|
|
closed atomic.Bool
|
|
closeOnce sync.Once
|
|
workerWG sync.WaitGroup
|
|
pendingWG sync.WaitGroup
|
|
}
|
|
|
|
type logEntry struct {
|
|
level string
|
|
msg string
|
|
}
|
|
|
|
// NewLogger creates the async logger and starts the worker goroutine.
|
|
// The log file is created under os.TempDir() using the required naming scheme.
|
|
func NewLogger() (*Logger, error) {
|
|
return NewLoggerWithSuffix("")
|
|
}
|
|
|
|
// NewLoggerWithSuffix creates a logger with an optional suffix in the filename.
|
|
// Useful for tests that need isolated log files within the same process.
|
|
func NewLoggerWithSuffix(suffix string) (*Logger, error) {
|
|
filename := fmt.Sprintf("codeagent-wrapper-%d", os.Getpid())
|
|
if suffix != "" {
|
|
filename += "-" + suffix
|
|
}
|
|
filename += ".log"
|
|
|
|
path := filepath.Clean(filepath.Join(os.TempDir(), filename))
|
|
|
|
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
l := &Logger{
|
|
path: path,
|
|
file: f,
|
|
writer: bufio.NewWriterSize(f, 4096),
|
|
ch: make(chan logEntry, 1000),
|
|
flushReq: make(chan chan struct{}, 1),
|
|
done: make(chan struct{}),
|
|
}
|
|
|
|
l.workerWG.Add(1)
|
|
go l.run()
|
|
|
|
return l, nil
|
|
}
|
|
|
|
// Path returns the underlying log file path (useful for tests/inspection).
|
|
func (l *Logger) Path() string {
|
|
if l == nil {
|
|
return ""
|
|
}
|
|
return l.path
|
|
}
|
|
|
|
// Info logs at INFO level.
|
|
func (l *Logger) Info(msg string) { l.log("INFO", msg) }
|
|
|
|
// Warn logs at WARN level.
|
|
func (l *Logger) Warn(msg string) { l.log("WARN", msg) }
|
|
|
|
// Debug logs at DEBUG level.
|
|
func (l *Logger) Debug(msg string) { l.log("DEBUG", msg) }
|
|
|
|
// Error logs at ERROR level.
|
|
func (l *Logger) Error(msg string) { l.log("ERROR", msg) }
|
|
|
|
// Close stops the worker and syncs the log file.
|
|
// The log file is NOT removed, allowing inspection after program exit.
|
|
// It is safe to call multiple times.
|
|
// Returns after a 5-second timeout if worker doesn't stop gracefully.
|
|
func (l *Logger) Close() error {
|
|
if l == nil {
|
|
return nil
|
|
}
|
|
|
|
var closeErr error
|
|
|
|
l.closeOnce.Do(func() {
|
|
l.closed.Store(true)
|
|
close(l.done)
|
|
close(l.ch)
|
|
|
|
// Wait for worker with timeout
|
|
workerDone := make(chan struct{})
|
|
go func() {
|
|
l.workerWG.Wait()
|
|
close(workerDone)
|
|
}()
|
|
|
|
select {
|
|
case <-workerDone:
|
|
// Worker stopped gracefully
|
|
case <-time.After(5 * time.Second):
|
|
// Worker timeout - proceed with cleanup anyway
|
|
closeErr = fmt.Errorf("logger worker timeout during close")
|
|
}
|
|
|
|
if err := l.writer.Flush(); err != nil && closeErr == nil {
|
|
closeErr = err
|
|
}
|
|
|
|
if err := l.file.Sync(); err != nil && closeErr == nil {
|
|
closeErr = err
|
|
}
|
|
|
|
if err := l.file.Close(); err != nil && closeErr == nil {
|
|
closeErr = err
|
|
}
|
|
|
|
// Log file is kept for debugging - NOT removed
|
|
// Users can manually clean up /tmp/codeagent-wrapper-*.log files
|
|
})
|
|
|
|
return closeErr
|
|
}
|
|
|
|
// RemoveLogFile removes the log file. Should only be called after Close().
|
|
func (l *Logger) RemoveLogFile() error {
|
|
if l == nil {
|
|
return nil
|
|
}
|
|
return os.Remove(l.path)
|
|
}
|
|
|
|
// Flush waits for all pending log entries to be written. Primarily for tests.
|
|
// Returns after a 5-second timeout to prevent indefinite blocking.
|
|
func (l *Logger) Flush() {
|
|
if l == nil {
|
|
return
|
|
}
|
|
|
|
// Wait for pending entries with timeout
|
|
done := make(chan struct{})
|
|
go func() {
|
|
l.pendingWG.Wait()
|
|
close(done)
|
|
}()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
select {
|
|
case <-done:
|
|
// All pending entries processed
|
|
case <-ctx.Done():
|
|
// Timeout - return without full flush
|
|
return
|
|
}
|
|
|
|
// Trigger writer flush
|
|
flushDone := make(chan struct{})
|
|
select {
|
|
case l.flushReq <- flushDone:
|
|
// Wait for flush to complete
|
|
select {
|
|
case <-flushDone:
|
|
// Flush completed
|
|
case <-time.After(1 * time.Second):
|
|
// Flush timeout
|
|
}
|
|
case <-l.done:
|
|
// Logger is closing
|
|
case <-time.After(1 * time.Second):
|
|
// Timeout sending flush request
|
|
}
|
|
}
|
|
|
|
func (l *Logger) log(level, msg string) {
|
|
if l == nil {
|
|
return
|
|
}
|
|
if l.closed.Load() {
|
|
return
|
|
}
|
|
|
|
entry := logEntry{level: level, msg: msg}
|
|
l.pendingWG.Add(1)
|
|
|
|
select {
|
|
case l.ch <- entry:
|
|
// Successfully sent to channel
|
|
case <-l.done:
|
|
// Logger is closing, drop this entry
|
|
l.pendingWG.Done()
|
|
return
|
|
}
|
|
}
|
|
|
|
func (l *Logger) run() {
|
|
defer l.workerWG.Done()
|
|
|
|
ticker := time.NewTicker(500 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case entry, ok := <-l.ch:
|
|
if !ok {
|
|
// Channel closed, final flush
|
|
_ = l.writer.Flush()
|
|
return
|
|
}
|
|
timestamp := time.Now().Format("2006-01-02 15:04:05.000")
|
|
pid := os.Getpid()
|
|
fmt.Fprintf(l.writer, "[%s] [PID:%d] %s: %s\n", timestamp, pid, entry.level, entry.msg)
|
|
l.pendingWG.Done()
|
|
|
|
case <-ticker.C:
|
|
_ = l.writer.Flush()
|
|
|
|
case flushDone := <-l.flushReq:
|
|
// Explicit flush request - flush writer and sync to disk
|
|
_ = l.writer.Flush()
|
|
_ = l.file.Sync()
|
|
close(flushDone)
|
|
}
|
|
}
|
|
}
|