mirror of
https://github.com/cexll/myclaude.git
synced 2026-02-11 03:23:50 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff301507fe | ||
|
|
93b72eba42 | ||
|
|
b01758e7e1 | ||
|
|
c51b38c671 | ||
|
|
b227fee225 | ||
|
|
2b7569335b |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@
|
|||||||
.pytest_cache
|
.pytest_cache
|
||||||
__pycache__
|
__pycache__
|
||||||
.coverage
|
.coverage
|
||||||
|
coverage.out
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
[中文](README_CN.md) [English](README.md)
|
||||||
|
|
||||||
# Claude Code Multi-Agent Workflow System
|
# Claude Code Multi-Agent Workflow System
|
||||||
|
|
||||||
[](https://smithery.ai/skills?ns=cexll&utm_source=github&utm_medium=badge)
|
[](https://smithery.ai/skills?ns=cexll&utm_source=github&utm_medium=badge)
|
||||||
@@ -5,7 +7,7 @@
|
|||||||
|
|
||||||
[](https://www.gnu.org/licenses/agpl-3.0)
|
[](https://www.gnu.org/licenses/agpl-3.0)
|
||||||
[](https://claude.ai/code)
|
[](https://claude.ai/code)
|
||||||
[](https://github.com/cexll/myclaude)
|
[](https://github.com/cexll/myclaude)
|
||||||
|
|
||||||
> AI-powered development automation with multi-backend execution (Codex/Claude/Gemini)
|
> AI-powered development automation with multi-backend execution (Codex/Claude/Gemini)
|
||||||
|
|
||||||
@@ -318,13 +320,10 @@ python3 install.py --module dev --force
|
|||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
### Core Guides
|
### Core Guides
|
||||||
- **[Architecture Overview](docs/architecture.md)** - System architecture and component design
|
|
||||||
- **[Codeagent-Wrapper Guide](docs/CODEAGENT-WRAPPER.md)** - Multi-backend execution wrapper
|
- **[Codeagent-Wrapper Guide](docs/CODEAGENT-WRAPPER.md)** - Multi-backend execution wrapper
|
||||||
- **[GitHub Workflow Guide](docs/GITHUB-WORKFLOW.md)** - Issue-to-PR automation
|
|
||||||
- **[Hooks Documentation](docs/HOOKS.md)** - Custom hooks and automation
|
- **[Hooks Documentation](docs/HOOKS.md)** - Custom hooks and automation
|
||||||
|
|
||||||
### Additional Resources
|
### Additional Resources
|
||||||
- **[Enterprise Workflow Ideas](docs/enterprise-workflow-ideas.md)** - Advanced patterns and best practices
|
|
||||||
- **[Installation Log](install.log)** - Installation history and troubleshooting
|
- **[Installation Log](install.log)** - Installation history and troubleshooting
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](https://www.gnu.org/licenses/agpl-3.0)
|
[](https://www.gnu.org/licenses/agpl-3.0)
|
||||||
[](https://claude.ai/code)
|
[](https://claude.ai/code)
|
||||||
[](https://github.com/cexll/myclaude)
|
[](https://github.com/cexll/myclaude)
|
||||||
|
|
||||||
> AI 驱动的开发自动化 - 多后端执行架构 (Codex/Claude/Gemini)
|
> AI 驱动的开发自动化 - 多后端执行架构 (Codex/Claude/Gemini)
|
||||||
|
|
||||||
|
|||||||
@@ -29,26 +29,20 @@ func (ClaudeBackend) BuildArgs(cfg *Config, targetArg string) []string {
|
|||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
args := []string{"-p"}
|
args := []string{"-p", "--dangerously-skip-permissions"}
|
||||||
|
|
||||||
// Only skip permissions when explicitly requested
|
// Only skip permissions when explicitly requested
|
||||||
if cfg.SkipPermissions {
|
// if cfg.SkipPermissions {
|
||||||
args = append(args, "--dangerously-skip-permissions")
|
// args = append(args, "--dangerously-skip-permissions")
|
||||||
}
|
// }
|
||||||
|
|
||||||
workdir := cfg.WorkDir
|
|
||||||
if workdir == "" {
|
|
||||||
workdir = defaultWorkdir
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Mode == "resume" {
|
if cfg.Mode == "resume" {
|
||||||
if cfg.SessionID != "" {
|
if cfg.SessionID != "" {
|
||||||
// Claude CLI uses -r <session_id> for resume.
|
// Claude CLI uses -r <session_id> for resume.
|
||||||
args = append(args, "-r", cfg.SessionID)
|
args = append(args, "-r", cfg.SessionID)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
args = append(args, "-C", workdir)
|
|
||||||
}
|
}
|
||||||
|
// Note: claude CLI doesn't support -C flag; workdir set via cmd.Dir
|
||||||
|
|
||||||
args = append(args, "--output-format", "stream-json", "--verbose", targetArg)
|
args = append(args, "--output-format", "stream-json", "--verbose", targetArg)
|
||||||
|
|
||||||
@@ -67,18 +61,12 @@ func (GeminiBackend) BuildArgs(cfg *Config, targetArg string) []string {
|
|||||||
}
|
}
|
||||||
args := []string{"-o", "stream-json", "-y"}
|
args := []string{"-o", "stream-json", "-y"}
|
||||||
|
|
||||||
workdir := cfg.WorkDir
|
|
||||||
if workdir == "" {
|
|
||||||
workdir = defaultWorkdir
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Mode == "resume" {
|
if cfg.Mode == "resume" {
|
||||||
if cfg.SessionID != "" {
|
if cfg.SessionID != "" {
|
||||||
args = append(args, "-r", cfg.SessionID)
|
args = append(args, "-r", cfg.SessionID)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
args = append(args, "-C", workdir)
|
|
||||||
}
|
}
|
||||||
|
// Note: gemini CLI doesn't support -C flag; workdir set via cmd.Dir
|
||||||
|
|
||||||
args = append(args, "-p", targetArg)
|
args = append(args, "-p", targetArg)
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ func TestClaudeBuildArgs_ModesAndPermissions(t *testing.T) {
|
|||||||
t.Run("new mode uses workdir without skip by default", func(t *testing.T) {
|
t.Run("new mode uses workdir without skip by default", func(t *testing.T) {
|
||||||
cfg := &Config{Mode: "new", WorkDir: "/repo"}
|
cfg := &Config{Mode: "new", WorkDir: "/repo"}
|
||||||
got := backend.BuildArgs(cfg, "todo")
|
got := backend.BuildArgs(cfg, "todo")
|
||||||
want := []string{"-p", "-C", "/repo", "--output-format", "stream-json", "--verbose", "todo"}
|
want := []string{"-p", "--dangerously-skip-permissions", "--output-format", "stream-json", "--verbose", "todo"}
|
||||||
if !reflect.DeepEqual(got, want) {
|
if !reflect.DeepEqual(got, want) {
|
||||||
t.Fatalf("got %v, want %v", got, want)
|
t.Fatalf("got %v, want %v", got, want)
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@ func TestClaudeBuildArgs_ModesAndPermissions(t *testing.T) {
|
|||||||
t.Run("new mode opt-in skip permissions with default workdir", func(t *testing.T) {
|
t.Run("new mode opt-in skip permissions with default workdir", func(t *testing.T) {
|
||||||
cfg := &Config{Mode: "new", SkipPermissions: true}
|
cfg := &Config{Mode: "new", SkipPermissions: true}
|
||||||
got := backend.BuildArgs(cfg, "-")
|
got := backend.BuildArgs(cfg, "-")
|
||||||
want := []string{"-p", "--dangerously-skip-permissions", "-C", defaultWorkdir, "--output-format", "stream-json", "--verbose", "-"}
|
want := []string{"-p", "--dangerously-skip-permissions", "--output-format", "stream-json", "--verbose", "-"}
|
||||||
if !reflect.DeepEqual(got, want) {
|
if !reflect.DeepEqual(got, want) {
|
||||||
t.Fatalf("got %v, want %v", got, want)
|
t.Fatalf("got %v, want %v", got, want)
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ func TestClaudeBuildArgs_ModesAndPermissions(t *testing.T) {
|
|||||||
t.Run("resume mode uses session id and omits workdir", func(t *testing.T) {
|
t.Run("resume mode uses session id and omits workdir", func(t *testing.T) {
|
||||||
cfg := &Config{Mode: "resume", SessionID: "sid-123", WorkDir: "/ignored"}
|
cfg := &Config{Mode: "resume", SessionID: "sid-123", WorkDir: "/ignored"}
|
||||||
got := backend.BuildArgs(cfg, "resume-task")
|
got := backend.BuildArgs(cfg, "resume-task")
|
||||||
want := []string{"-p", "-r", "sid-123", "--output-format", "stream-json", "--verbose", "resume-task"}
|
want := []string{"-p", "--dangerously-skip-permissions", "-r", "sid-123", "--output-format", "stream-json", "--verbose", "resume-task"}
|
||||||
if !reflect.DeepEqual(got, want) {
|
if !reflect.DeepEqual(got, want) {
|
||||||
t.Fatalf("got %v, want %v", got, want)
|
t.Fatalf("got %v, want %v", got, want)
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ func TestClaudeBuildArgs_ModesAndPermissions(t *testing.T) {
|
|||||||
t.Run("resume mode without session still returns base flags", func(t *testing.T) {
|
t.Run("resume mode without session still returns base flags", func(t *testing.T) {
|
||||||
cfg := &Config{Mode: "resume", WorkDir: "/ignored"}
|
cfg := &Config{Mode: "resume", WorkDir: "/ignored"}
|
||||||
got := backend.BuildArgs(cfg, "follow-up")
|
got := backend.BuildArgs(cfg, "follow-up")
|
||||||
want := []string{"-p", "--output-format", "stream-json", "--verbose", "follow-up"}
|
want := []string{"-p", "--dangerously-skip-permissions", "--output-format", "stream-json", "--verbose", "follow-up"}
|
||||||
if !reflect.DeepEqual(got, want) {
|
if !reflect.DeepEqual(got, want) {
|
||||||
t.Fatalf("got %v, want %v", got, want)
|
t.Fatalf("got %v, want %v", got, want)
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ func TestClaudeBuildArgs_GeminiAndCodexModes(t *testing.T) {
|
|||||||
backend := GeminiBackend{}
|
backend := GeminiBackend{}
|
||||||
cfg := &Config{Mode: "new", WorkDir: "/workspace"}
|
cfg := &Config{Mode: "new", WorkDir: "/workspace"}
|
||||||
got := backend.BuildArgs(cfg, "task")
|
got := backend.BuildArgs(cfg, "task")
|
||||||
want := []string{"-o", "stream-json", "-y", "-C", "/workspace", "-p", "task"}
|
want := []string{"-o", "stream-json", "-y", "-p", "task"}
|
||||||
if !reflect.DeepEqual(got, want) {
|
if !reflect.DeepEqual(got, want) {
|
||||||
t.Fatalf("got %v, want %v", got, want)
|
t.Fatalf("got %v, want %v", got, want)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ type commandRunner interface {
|
|||||||
StdoutPipe() (io.ReadCloser, error)
|
StdoutPipe() (io.ReadCloser, error)
|
||||||
StdinPipe() (io.WriteCloser, error)
|
StdinPipe() (io.WriteCloser, error)
|
||||||
SetStderr(io.Writer)
|
SetStderr(io.Writer)
|
||||||
|
SetDir(string)
|
||||||
Process() processHandle
|
Process() processHandle
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +73,12 @@ func (r *realCmd) SetStderr(w io.Writer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *realCmd) SetDir(dir string) {
|
||||||
|
if r.cmd != nil {
|
||||||
|
r.cmd.Dir = dir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *realCmd) Process() processHandle {
|
func (r *realCmd) Process() processHandle {
|
||||||
if r == nil || r.cmd == nil || r.cmd.Process == nil {
|
if r == nil || r.cmd == nil || r.cmd.Process == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -577,6 +584,12 @@ func runCodexTaskWithContext(parentCtx context.Context, taskSpec TaskSpec, backe
|
|||||||
|
|
||||||
cmd := newCommandRunner(ctx, commandName, codexArgs...)
|
cmd := newCommandRunner(ctx, commandName, codexArgs...)
|
||||||
|
|
||||||
|
// For backends that don't support -C flag (claude, gemini), set working directory via cmd.Dir
|
||||||
|
// Codex passes workdir via -C flag, so we skip setting Dir for it to avoid conflicts
|
||||||
|
if cfg.Mode != "resume" && commandName != "codex" && cfg.WorkDir != "" {
|
||||||
|
cmd.SetDir(cfg.WorkDir)
|
||||||
|
}
|
||||||
|
|
||||||
stderrWriters := []io.Writer{stderrBuf}
|
stderrWriters := []io.Writer{stderrBuf}
|
||||||
if stderrLogger != nil {
|
if stderrLogger != nil {
|
||||||
stderrWriters = append(stderrWriters, stderrLogger)
|
stderrWriters = append(stderrWriters, stderrLogger)
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ func (f *execFakeRunner) StdinPipe() (io.WriteCloser, error) {
|
|||||||
return &writeCloserStub{}, nil
|
return &writeCloserStub{}, nil
|
||||||
}
|
}
|
||||||
func (f *execFakeRunner) SetStderr(io.Writer) {}
|
func (f *execFakeRunner) SetStderr(io.Writer) {}
|
||||||
|
func (f *execFakeRunner) SetDir(string) {}
|
||||||
func (f *execFakeRunner) Process() processHandle {
|
func (f *execFakeRunner) Process() processHandle {
|
||||||
if f.process != nil {
|
if f.process != nil {
|
||||||
return f.process
|
return f.process
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
version = "5.2.0"
|
version = "5.2.2"
|
||||||
defaultWorkdir = "."
|
defaultWorkdir = "."
|
||||||
defaultTimeout = 7200 // seconds
|
defaultTimeout = 7200 // seconds
|
||||||
codexLogLineLimit = 1000
|
codexLogLineLimit = 1000
|
||||||
|
|||||||
@@ -250,6 +250,10 @@ func (d *drainBlockingCmd) SetStderr(w io.Writer) {
|
|||||||
d.inner.SetStderr(w)
|
d.inner.SetStderr(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *drainBlockingCmd) SetDir(dir string) {
|
||||||
|
d.inner.SetDir(dir)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *drainBlockingCmd) Process() processHandle {
|
func (d *drainBlockingCmd) Process() processHandle {
|
||||||
return d.inner.Process()
|
return d.inner.Process()
|
||||||
}
|
}
|
||||||
@@ -504,6 +508,8 @@ func (f *fakeCmd) SetStderr(w io.Writer) {
|
|||||||
f.stderr = w
|
f.stderr = w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *fakeCmd) SetDir(string) {}
|
||||||
|
|
||||||
func (f *fakeCmd) Process() processHandle {
|
func (f *fakeCmd) Process() processHandle {
|
||||||
if f == nil {
|
if f == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -1371,7 +1377,7 @@ func TestBackendBuildArgs_ClaudeBackend(t *testing.T) {
|
|||||||
backend := ClaudeBackend{}
|
backend := ClaudeBackend{}
|
||||||
cfg := &Config{Mode: "new", WorkDir: defaultWorkdir}
|
cfg := &Config{Mode: "new", WorkDir: defaultWorkdir}
|
||||||
got := backend.BuildArgs(cfg, "todo")
|
got := backend.BuildArgs(cfg, "todo")
|
||||||
want := []string{"-p", "-C", defaultWorkdir, "--output-format", "stream-json", "--verbose", "todo"}
|
want := []string{"-p", "--dangerously-skip-permissions", "--output-format", "stream-json", "--verbose", "todo"}
|
||||||
if len(got) != len(want) {
|
if len(got) != len(want) {
|
||||||
t.Fatalf("length mismatch")
|
t.Fatalf("length mismatch")
|
||||||
}
|
}
|
||||||
@@ -1392,7 +1398,7 @@ func TestClaudeBackendBuildArgs_OutputValidation(t *testing.T) {
|
|||||||
target := "ensure-flags"
|
target := "ensure-flags"
|
||||||
|
|
||||||
args := backend.BuildArgs(cfg, target)
|
args := backend.BuildArgs(cfg, target)
|
||||||
expectedPrefix := []string{"-p", "--output-format", "stream-json", "--verbose"}
|
expectedPrefix := []string{"-p", "--dangerously-skip-permissions", "--output-format", "stream-json", "--verbose"}
|
||||||
|
|
||||||
if len(args) != len(expectedPrefix)+1 {
|
if len(args) != len(expectedPrefix)+1 {
|
||||||
t.Fatalf("args length=%d, want %d", len(args), len(expectedPrefix)+1)
|
t.Fatalf("args length=%d, want %d", len(args), len(expectedPrefix)+1)
|
||||||
@@ -1411,7 +1417,7 @@ func TestBackendBuildArgs_GeminiBackend(t *testing.T) {
|
|||||||
backend := GeminiBackend{}
|
backend := GeminiBackend{}
|
||||||
cfg := &Config{Mode: "new"}
|
cfg := &Config{Mode: "new"}
|
||||||
got := backend.BuildArgs(cfg, "task")
|
got := backend.BuildArgs(cfg, "task")
|
||||||
want := []string{"-o", "stream-json", "-y", "-C", defaultWorkdir, "-p", "task"}
|
want := []string{"-o", "stream-json", "-y", "-p", "task"}
|
||||||
if len(got) != len(want) {
|
if len(got) != len(want) {
|
||||||
t.Fatalf("length mismatch")
|
t.Fatalf("length mismatch")
|
||||||
}
|
}
|
||||||
@@ -2684,7 +2690,7 @@ func TestVersionFlag(t *testing.T) {
|
|||||||
t.Errorf("exit = %d, want 0", code)
|
t.Errorf("exit = %d, want 0", code)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
want := "codeagent-wrapper version 5.2.0\n"
|
want := "codeagent-wrapper version 5.2.2\n"
|
||||||
if output != want {
|
if output != want {
|
||||||
t.Fatalf("output = %q, want %q", output, want)
|
t.Fatalf("output = %q, want %q", output, want)
|
||||||
}
|
}
|
||||||
@@ -2698,7 +2704,7 @@ func TestVersionShortFlag(t *testing.T) {
|
|||||||
t.Errorf("exit = %d, want 0", code)
|
t.Errorf("exit = %d, want 0", code)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
want := "codeagent-wrapper version 5.2.0\n"
|
want := "codeagent-wrapper version 5.2.2\n"
|
||||||
if output != want {
|
if output != want {
|
||||||
t.Fatalf("output = %q, want %q", output, want)
|
t.Fatalf("output = %q, want %q", output, want)
|
||||||
}
|
}
|
||||||
@@ -2712,7 +2718,7 @@ func TestVersionLegacyAlias(t *testing.T) {
|
|||||||
t.Errorf("exit = %d, want 0", code)
|
t.Errorf("exit = %d, want 0", code)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
want := "codex-wrapper version 5.2.0\n"
|
want := "codex-wrapper version 5.2.2\n"
|
||||||
if output != want {
|
if output != want {
|
||||||
t.Fatalf("output = %q, want %q", output, want)
|
t.Fatalf("output = %q, want %q", output, want)
|
||||||
}
|
}
|
||||||
|
|||||||
30
config.json
30
config.json
@@ -20,9 +20,33 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "copy_file",
|
"type": "copy_file",
|
||||||
"source": "skills/codex/SKILL.md",
|
"source": "skills/codeagent/SKILL.md",
|
||||||
"target": "skills/codex/SKILL.md",
|
"target": "skills/codeagent/SKILL.md",
|
||||||
"description": "Install codex skill"
|
"description": "Install codeagent skill"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "copy_file",
|
||||||
|
"source": "skills/product-requirements/SKILL.md",
|
||||||
|
"target": "skills/product-requirements/SKILL.md",
|
||||||
|
"description": "Install product-requirements skill"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "copy_file",
|
||||||
|
"source": "skills/prototype-prompt-generator/SKILL.md",
|
||||||
|
"target": "skills/prototype-prompt-generator/SKILL.md",
|
||||||
|
"description": "Install prototype-prompt-generator skill"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "copy_file",
|
||||||
|
"source": "skills/prototype-prompt-generator/references/prompt-structure.md",
|
||||||
|
"target": "skills/prototype-prompt-generator/references/prompt-structure.md",
|
||||||
|
"description": "Install prototype-prompt-generator prompt structure reference"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "copy_file",
|
||||||
|
"source": "skills/prototype-prompt-generator/references/design-systems.md",
|
||||||
|
"target": "skills/prototype-prompt-generator/references/design-systems.md",
|
||||||
|
"description": "Install prototype-prompt-generator design systems reference"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "run_command",
|
"type": "run_command",
|
||||||
|
|||||||
Reference in New Issue
Block a user