mirror of
https://github.com/cexll/myclaude.git
synced 2026-02-04 02:20:42 +08:00
Co-authored-by: tytsxai <tytsxai@users.noreply.github.com>
This commit is contained in:
154
README.md
154
README.md
@@ -132,6 +132,59 @@ Requirements → Architecture → Sprint Plan → Development → Review → QA
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Version Requirements
|
||||||
|
|
||||||
|
### Codex CLI
|
||||||
|
**Minimum version:** Check compatibility with your installation
|
||||||
|
|
||||||
|
The codeagent-wrapper uses these Codex CLI features:
|
||||||
|
- `codex e` - Execute commands (shorthand for `codex exec`)
|
||||||
|
- `--skip-git-repo-check` - Skip git repository validation
|
||||||
|
- `--json` - JSON stream output format
|
||||||
|
- `-C <workdir>` - Set working directory
|
||||||
|
- `resume <session_id>` - Resume previous sessions
|
||||||
|
|
||||||
|
**Verify Codex CLI is installed:**
|
||||||
|
```bash
|
||||||
|
which codex
|
||||||
|
codex --version
|
||||||
|
```
|
||||||
|
|
||||||
|
### Claude CLI
|
||||||
|
**Minimum version:** Check compatibility with your installation
|
||||||
|
|
||||||
|
Required features:
|
||||||
|
- `--output-format stream-json` - Streaming JSON output format
|
||||||
|
- `--setting-sources` - Control setting sources (prevents infinite recursion)
|
||||||
|
- `--dangerously-skip-permissions` - Skip permission prompts (use with caution)
|
||||||
|
- `-p` - Prompt input flag
|
||||||
|
- `-r <session_id>` - Resume sessions
|
||||||
|
|
||||||
|
**Security Note:** The wrapper only adds `--dangerously-skip-permissions` for Claude when explicitly enabled (e.g. `--skip-permissions` / `CODEAGENT_SKIP_PERMISSIONS=true`). Keep it disabled unless you understand the risk.
|
||||||
|
|
||||||
|
**Verify Claude CLI is installed:**
|
||||||
|
```bash
|
||||||
|
which claude
|
||||||
|
claude --version
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gemini CLI
|
||||||
|
**Minimum version:** Check compatibility with your installation
|
||||||
|
|
||||||
|
Required features:
|
||||||
|
- `-o stream-json` - JSON stream output format
|
||||||
|
- `-y` - Auto-approve prompts (non-interactive mode)
|
||||||
|
- `-r <session_id>` - Resume sessions
|
||||||
|
- `-p` - Prompt input flag
|
||||||
|
|
||||||
|
**Verify Gemini CLI is installed:**
|
||||||
|
```bash
|
||||||
|
which gemini
|
||||||
|
gemini --version
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Modular Installation (Recommended)
|
### Modular Installation (Recommended)
|
||||||
@@ -163,15 +216,39 @@ python3 install.py --force
|
|||||||
|
|
||||||
```
|
```
|
||||||
~/.claude/
|
~/.claude/
|
||||||
├── CLAUDE.md # Core instructions and role definition
|
├── bin/
|
||||||
├── commands/ # Slash commands (/dev, /code, etc.)
|
│ └── codeagent-wrapper # Main executable
|
||||||
├── agents/ # Agent definitions
|
├── CLAUDE.md # Core instructions and role definition
|
||||||
|
├── commands/ # Slash commands (/dev, /code, etc.)
|
||||||
|
├── agents/ # Agent definitions
|
||||||
├── skills/
|
├── skills/
|
||||||
│ └── codex/
|
│ └── codex/
|
||||||
│ └── SKILL.md # Codex integration skill
|
│ └── SKILL.md # Codex integration skill
|
||||||
└── installed_modules.json # Installation status
|
├── config.json # Configuration
|
||||||
|
└── installed_modules.json # Installation status
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Customizing Installation Directory
|
||||||
|
|
||||||
|
By default, myclaude installs to `~/.claude`. You can customize this using the `INSTALL_DIR` environment variable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install to custom directory
|
||||||
|
INSTALL_DIR=/opt/myclaude bash install.sh
|
||||||
|
|
||||||
|
# Update your PATH accordingly
|
||||||
|
export PATH="/opt/myclaude/bin:$PATH"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Directory Structure:**
|
||||||
|
- `$INSTALL_DIR/bin/` - codeagent-wrapper binary
|
||||||
|
- `$INSTALL_DIR/skills/` - Skill definitions
|
||||||
|
- `$INSTALL_DIR/config.json` - Configuration file
|
||||||
|
- `$INSTALL_DIR/commands/` - Slash command definitions
|
||||||
|
- `$INSTALL_DIR/agents/` - Agent definitions
|
||||||
|
|
||||||
|
**Note:** When using a custom installation directory, ensure that `$INSTALL_DIR/bin` is added to your `PATH` environment variable.
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
Edit `config.json` to customize:
|
Edit `config.json` to customize:
|
||||||
@@ -295,7 +372,7 @@ setx PATH "%USERPROFILE%\bin;%PATH%"
|
|||||||
**Codex wrapper not found:**
|
**Codex wrapper not found:**
|
||||||
```bash
|
```bash
|
||||||
# Check PATH
|
# Check PATH
|
||||||
echo $PATH | grep -q "$HOME/bin" || echo 'export PATH="$HOME/bin:$PATH"' >> ~/.zshrc
|
echo $PATH | grep -q "$HOME/.claude/bin" || echo 'export PATH="$HOME/.claude/bin:$PATH"' >> ~/.zshrc
|
||||||
|
|
||||||
# Reinstall
|
# Reinstall
|
||||||
bash install.sh
|
bash install.sh
|
||||||
@@ -315,6 +392,71 @@ cat ~/.claude/installed_modules.json
|
|||||||
python3 install.py --module dev --force
|
python3 install.py --module dev --force
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Version Compatibility Issues
|
||||||
|
|
||||||
|
**Backend CLI not found:**
|
||||||
|
```bash
|
||||||
|
# Check if backend CLIs are installed
|
||||||
|
which codex
|
||||||
|
which claude
|
||||||
|
which gemini
|
||||||
|
|
||||||
|
# Install missing backends
|
||||||
|
# Codex: Follow installation instructions at https://codex.docs
|
||||||
|
# Claude: Follow installation instructions at https://claude.ai/docs
|
||||||
|
# Gemini: Follow installation instructions at https://ai.google.dev/docs
|
||||||
|
```
|
||||||
|
|
||||||
|
**Unsupported CLI flags:**
|
||||||
|
```bash
|
||||||
|
# If you see errors like "unknown flag" or "invalid option"
|
||||||
|
|
||||||
|
# Check backend CLI version
|
||||||
|
codex --version
|
||||||
|
claude --version
|
||||||
|
gemini --version
|
||||||
|
|
||||||
|
# For Codex: Ensure it supports `e`, `--skip-git-repo-check`, `--json`, `-C`, and `resume`
|
||||||
|
# For Claude: Ensure it supports `--output-format stream-json`, `--setting-sources`, `-r`
|
||||||
|
# For Gemini: Ensure it supports `-o stream-json`, `-y`, `-r`, `-p`
|
||||||
|
|
||||||
|
# Update your backend CLI to the latest version if needed
|
||||||
|
```
|
||||||
|
|
||||||
|
**JSON parsing errors:**
|
||||||
|
```bash
|
||||||
|
# If you see "failed to parse JSON output" errors
|
||||||
|
|
||||||
|
# Verify the backend outputs stream-json format
|
||||||
|
codex e --json "test task" # Should output newline-delimited JSON
|
||||||
|
claude --output-format stream-json -p "test" # Should output stream JSON
|
||||||
|
|
||||||
|
# If not, your backend CLI version may be too old or incompatible
|
||||||
|
```
|
||||||
|
|
||||||
|
**Infinite recursion with Claude backend:**
|
||||||
|
```bash
|
||||||
|
# The wrapper prevents this with `--setting-sources ""` flag
|
||||||
|
# If you still see recursion, ensure your Claude CLI supports this flag
|
||||||
|
|
||||||
|
claude --help | grep "setting-sources"
|
||||||
|
|
||||||
|
# If flag is not supported, upgrade Claude CLI
|
||||||
|
```
|
||||||
|
|
||||||
|
**Session resume failures:**
|
||||||
|
```bash
|
||||||
|
# Check if session ID is valid
|
||||||
|
codex history # List recent sessions
|
||||||
|
claude history
|
||||||
|
|
||||||
|
# Ensure backend CLI supports session resumption
|
||||||
|
codex resume <session_id> "test" # Should continue from previous session
|
||||||
|
claude -r <session_id> "test"
|
||||||
|
|
||||||
|
# If not supported, use new sessions instead of resume mode
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|||||||
36
README_CN.md
36
README_CN.md
@@ -152,15 +152,39 @@ python3 install.py --force
|
|||||||
|
|
||||||
```
|
```
|
||||||
~/.claude/
|
~/.claude/
|
||||||
├── CLAUDE.md # 核心指令和角色定义
|
├── bin/
|
||||||
├── commands/ # 斜杠命令 (/dev, /code 等)
|
│ └── codeagent-wrapper # 主可执行文件
|
||||||
├── agents/ # 智能体定义
|
├── CLAUDE.md # 核心指令和角色定义
|
||||||
|
├── commands/ # 斜杠命令 (/dev, /code 等)
|
||||||
|
├── agents/ # 智能体定义
|
||||||
├── skills/
|
├── skills/
|
||||||
│ └── codex/
|
│ └── codex/
|
||||||
│ └── SKILL.md # Codex 集成技能
|
│ └── SKILL.md # Codex 集成技能
|
||||||
└── installed_modules.json # 安装状态
|
├── config.json # 配置文件
|
||||||
|
└── installed_modules.json # 安装状态
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 自定义安装目录
|
||||||
|
|
||||||
|
默认情况下,myclaude 安装到 `~/.claude`。您可以使用 `INSTALL_DIR` 环境变量自定义安装目录:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装到自定义目录
|
||||||
|
INSTALL_DIR=/opt/myclaude bash install.sh
|
||||||
|
|
||||||
|
# 相应更新您的 PATH
|
||||||
|
export PATH="/opt/myclaude/bin:$PATH"
|
||||||
|
```
|
||||||
|
|
||||||
|
**目录结构:**
|
||||||
|
- `$INSTALL_DIR/bin/` - codeagent-wrapper 可执行文件
|
||||||
|
- `$INSTALL_DIR/skills/` - 技能定义
|
||||||
|
- `$INSTALL_DIR/config.json` - 配置文件
|
||||||
|
- `$INSTALL_DIR/commands/` - 斜杠命令定义
|
||||||
|
- `$INSTALL_DIR/agents/` - 智能体定义
|
||||||
|
|
||||||
|
**注意:** 使用自定义安装目录时,请确保将 `$INSTALL_DIR/bin` 添加到您的 `PATH` 环境变量中。
|
||||||
|
|
||||||
### 配置
|
### 配置
|
||||||
|
|
||||||
编辑 `config.json` 自定义:
|
编辑 `config.json` 自定义:
|
||||||
@@ -284,7 +308,7 @@ setx PATH "%USERPROFILE%\bin;%PATH%"
|
|||||||
**Codex wrapper 未找到:**
|
**Codex wrapper 未找到:**
|
||||||
```bash
|
```bash
|
||||||
# 检查 PATH
|
# 检查 PATH
|
||||||
echo $PATH | grep -q "$HOME/bin" || echo 'export PATH="$HOME/bin:$PATH"' >> ~/.zshrc
|
echo $PATH | grep -q "$HOME/.claude/bin" || echo 'export PATH="$HOME/.claude/bin:$PATH"' >> ~/.zshrc
|
||||||
|
|
||||||
# 重新安装
|
# 重新安装
|
||||||
bash install.sh
|
bash install.sh
|
||||||
|
|||||||
@@ -26,15 +26,17 @@ func (ClaudeBackend) Command() string {
|
|||||||
return "claude"
|
return "claude"
|
||||||
}
|
}
|
||||||
func (ClaudeBackend) BuildArgs(cfg *Config, targetArg string) []string {
|
func (ClaudeBackend) BuildArgs(cfg *Config, targetArg string) []string {
|
||||||
|
return buildClaudeArgs(cfg, targetArg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildClaudeArgs(cfg *Config, targetArg string) []string {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
args := []string{"-p", "--dangerously-skip-permissions"}
|
args := []string{"-p"}
|
||||||
|
if cfg.SkipPermissions {
|
||||||
// Only skip permissions when explicitly requested
|
args = append(args, "--dangerously-skip-permissions")
|
||||||
// if cfg.SkipPermissions {
|
}
|
||||||
// args = append(args, "--dangerously-skip-permissions")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Prevent infinite recursion: disable all setting sources (user, project, local)
|
// Prevent infinite recursion: disable all setting sources (user, project, local)
|
||||||
// This ensures a clean execution environment without CLAUDE.md or skills that would trigger codeagent
|
// This ensures a clean execution environment without CLAUDE.md or skills that would trigger codeagent
|
||||||
@@ -60,6 +62,10 @@ func (GeminiBackend) Command() string {
|
|||||||
return "gemini"
|
return "gemini"
|
||||||
}
|
}
|
||||||
func (GeminiBackend) BuildArgs(cfg *Config, targetArg string) []string {
|
func (GeminiBackend) BuildArgs(cfg *Config, targetArg string) []string {
|
||||||
|
return buildGeminiArgs(cfg, targetArg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildGeminiArgs(cfg *Config, targetArg string) []string {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -8,16 +9,16 @@ import (
|
|||||||
func TestClaudeBuildArgs_ModesAndPermissions(t *testing.T) {
|
func TestClaudeBuildArgs_ModesAndPermissions(t *testing.T) {
|
||||||
backend := ClaudeBackend{}
|
backend := ClaudeBackend{}
|
||||||
|
|
||||||
t.Run("new mode uses workdir without skip by default", func(t *testing.T) {
|
t.Run("new mode omits skip-permissions 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", "--dangerously-skip-permissions", "--setting-sources", "", "--output-format", "stream-json", "--verbose", "todo"}
|
want := []string{"-p", "--setting-sources", "", "--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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("new mode opt-in skip permissions with default workdir", func(t *testing.T) {
|
t.Run("new mode can opt-in skip-permissions", 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", "--setting-sources", "", "--output-format", "stream-json", "--verbose", "-"}
|
want := []string{"-p", "--dangerously-skip-permissions", "--setting-sources", "", "--output-format", "stream-json", "--verbose", "-"}
|
||||||
@@ -26,10 +27,10 @@ func TestClaudeBuildArgs_ModesAndPermissions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("resume mode uses session id and omits workdir", func(t *testing.T) {
|
t.Run("resume mode includes session id", 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", "--dangerously-skip-permissions", "--setting-sources", "", "-r", "sid-123", "--output-format", "stream-json", "--verbose", "resume-task"}
|
want := []string{"-p", "--setting-sources", "", "-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 +39,16 @@ 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", "--dangerously-skip-permissions", "--setting-sources", "", "--output-format", "stream-json", "--verbose", "follow-up"}
|
want := []string{"-p", "--setting-sources", "", "--output-format", "stream-json", "--verbose", "follow-up"}
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("got %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("resume mode can opt-in skip permissions", func(t *testing.T) {
|
||||||
|
cfg := &Config{Mode: "resume", SessionID: "sid-123", SkipPermissions: true}
|
||||||
|
got := backend.BuildArgs(cfg, "resume-task")
|
||||||
|
want := []string{"-p", "--dangerously-skip-permissions", "--setting-sources", "", "-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)
|
||||||
}
|
}
|
||||||
@@ -89,7 +99,11 @@ func TestClaudeBuildArgs_GeminiAndCodexModes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("codex build args passthrough remains intact", func(t *testing.T) {
|
t.Run("codex build args omits bypass flag by default", func(t *testing.T) {
|
||||||
|
const key = "CODEX_BYPASS_SANDBOX"
|
||||||
|
t.Cleanup(func() { os.Unsetenv(key) })
|
||||||
|
os.Unsetenv(key)
|
||||||
|
|
||||||
backend := CodexBackend{}
|
backend := CodexBackend{}
|
||||||
cfg := &Config{Mode: "new", WorkDir: "/tmp"}
|
cfg := &Config{Mode: "new", WorkDir: "/tmp"}
|
||||||
got := backend.BuildArgs(cfg, "task")
|
got := backend.BuildArgs(cfg, "task")
|
||||||
@@ -98,6 +112,20 @@ func TestClaudeBuildArgs_GeminiAndCodexModes(t *testing.T) {
|
|||||||
t.Fatalf("got %v, want %v", got, want)
|
t.Fatalf("got %v, want %v", got, want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("codex build args includes bypass flag when enabled", func(t *testing.T) {
|
||||||
|
const key = "CODEX_BYPASS_SANDBOX"
|
||||||
|
t.Cleanup(func() { os.Unsetenv(key) })
|
||||||
|
os.Setenv(key, "true")
|
||||||
|
|
||||||
|
backend := CodexBackend{}
|
||||||
|
cfg := &Config{Mode: "new", WorkDir: "/tmp"}
|
||||||
|
got := backend.BuildArgs(cfg, "task")
|
||||||
|
want := []string{"e", "--dangerously-bypass-approvals-and-sandbox", "--skip-git-repo-check", "-C", "/tmp", "--json", "task"}
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("got %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClaudeBuildArgs_BackendMetadata(t *testing.T) {
|
func TestClaudeBuildArgs_BackendMetadata(t *testing.T) {
|
||||||
|
|||||||
@@ -164,6 +164,9 @@ func parseParallelConfig(data []byte) (*ParallelConfig, error) {
|
|||||||
if content == "" {
|
if content == "" {
|
||||||
return nil, fmt.Errorf("task block #%d (%q) missing content", taskIndex, task.ID)
|
return nil, fmt.Errorf("task block #%d (%q) missing content", taskIndex, task.ID)
|
||||||
}
|
}
|
||||||
|
if task.Mode == "resume" && strings.TrimSpace(task.SessionID) == "" {
|
||||||
|
return nil, fmt.Errorf("task block #%d (%q) has empty session_id", taskIndex, task.ID)
|
||||||
|
}
|
||||||
if _, exists := seen[task.ID]; exists {
|
if _, exists := seen[task.ID]; exists {
|
||||||
return nil, fmt.Errorf("task block #%d has duplicate id: %s", taskIndex, task.ID)
|
return nil, fmt.Errorf("task block #%d has duplicate id: %s", taskIndex, task.ID)
|
||||||
}
|
}
|
||||||
@@ -232,7 +235,10 @@ func parseArgs() (*Config, error) {
|
|||||||
return nil, fmt.Errorf("resume mode requires: resume <session_id> <task>")
|
return nil, fmt.Errorf("resume mode requires: resume <session_id> <task>")
|
||||||
}
|
}
|
||||||
cfg.Mode = "resume"
|
cfg.Mode = "resume"
|
||||||
cfg.SessionID = args[1]
|
cfg.SessionID = strings.TrimSpace(args[1])
|
||||||
|
if cfg.SessionID == "" {
|
||||||
|
return nil, fmt.Errorf("resume mode requires non-empty session_id")
|
||||||
|
}
|
||||||
cfg.Task = args[2]
|
cfg.Task = args[2]
|
||||||
cfg.ExplicitStdin = (args[2] == "-")
|
cfg.ExplicitStdin = (args[2] == "-")
|
||||||
if len(args) > 3 {
|
if len(args) > 3 {
|
||||||
|
|||||||
@@ -509,23 +509,43 @@ func generateFinalOutput(results []TaskResult) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildCodexArgs(cfg *Config, targetArg string) []string {
|
func buildCodexArgs(cfg *Config, targetArg string) []string {
|
||||||
if cfg.Mode == "resume" {
|
if cfg == nil {
|
||||||
return []string{
|
panic("buildCodexArgs: nil config")
|
||||||
"e",
|
}
|
||||||
"--skip-git-repo-check",
|
|
||||||
"--json",
|
var resumeSessionID string
|
||||||
"resume",
|
isResume := cfg.Mode == "resume"
|
||||||
cfg.SessionID,
|
if isResume {
|
||||||
targetArg,
|
resumeSessionID = strings.TrimSpace(cfg.SessionID)
|
||||||
|
if resumeSessionID == "" {
|
||||||
|
logError("invalid config: resume mode requires non-empty session_id")
|
||||||
|
isResume = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []string{
|
|
||||||
"e",
|
args := []string{"e"}
|
||||||
"--skip-git-repo-check",
|
|
||||||
|
if envFlagEnabled("CODEX_BYPASS_SANDBOX") {
|
||||||
|
logWarn("CODEX_BYPASS_SANDBOX=true: running without approval/sandbox protection")
|
||||||
|
args = append(args, "--dangerously-bypass-approvals-and-sandbox")
|
||||||
|
}
|
||||||
|
|
||||||
|
args = append(args, "--skip-git-repo-check")
|
||||||
|
|
||||||
|
if isResume {
|
||||||
|
return append(args,
|
||||||
|
"--json",
|
||||||
|
"resume",
|
||||||
|
resumeSessionID,
|
||||||
|
targetArg,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(args,
|
||||||
"-C", cfg.WorkDir,
|
"-C", cfg.WorkDir,
|
||||||
"--json",
|
"--json",
|
||||||
targetArg,
|
targetArg,
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCodexTask(taskSpec TaskSpec, silent bool, timeoutSec int) TaskResult {
|
func runCodexTask(taskSpec TaskSpec, silent bool, timeoutSec int) TaskResult {
|
||||||
@@ -576,6 +596,12 @@ func runCodexTaskWithContext(parentCtx context.Context, taskSpec TaskSpec, backe
|
|||||||
cfg.WorkDir = defaultWorkdir
|
cfg.WorkDir = defaultWorkdir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.Mode == "resume" && strings.TrimSpace(cfg.SessionID) == "" {
|
||||||
|
result.ExitCode = 1
|
||||||
|
result.Error = "resume mode requires non-empty session_id"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
useStdin := taskSpec.UseStdin
|
useStdin := taskSpec.UseStdin
|
||||||
targetArg := taskSpec.Task
|
targetArg := taskSpec.Task
|
||||||
if useStdin {
|
if useStdin {
|
||||||
@@ -745,6 +771,10 @@ func runCodexTaskWithContext(parentCtx context.Context, taskSpec TaskSpec, backe
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
select {
|
||||||
|
case completeSeen <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
parseCh <- parseResult{message: msg, threadID: tid}
|
parseCh <- parseResult{message: msg, threadID: tid}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -244,6 +245,10 @@ func TestExecutorHelperCoverage(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("generateFinalOutputAndArgs", func(t *testing.T) {
|
t.Run("generateFinalOutputAndArgs", func(t *testing.T) {
|
||||||
|
const key = "CODEX_BYPASS_SANDBOX"
|
||||||
|
t.Cleanup(func() { os.Unsetenv(key) })
|
||||||
|
os.Unsetenv(key)
|
||||||
|
|
||||||
out := generateFinalOutput([]TaskResult{
|
out := generateFinalOutput([]TaskResult{
|
||||||
{TaskID: "ok", ExitCode: 0},
|
{TaskID: "ok", ExitCode: 0},
|
||||||
{TaskID: "fail", ExitCode: 1, Error: "boom"},
|
{TaskID: "fail", ExitCode: 1, Error: "boom"},
|
||||||
@@ -257,11 +262,11 @@ func TestExecutorHelperCoverage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
args := buildCodexArgs(&Config{Mode: "new", WorkDir: "/tmp"}, "task")
|
args := buildCodexArgs(&Config{Mode: "new", WorkDir: "/tmp"}, "task")
|
||||||
if len(args) == 0 || args[3] != "/tmp" {
|
if !slices.Equal(args, []string{"e", "--skip-git-repo-check", "-C", "/tmp", "--json", "task"}) {
|
||||||
t.Fatalf("unexpected codex args: %+v", args)
|
t.Fatalf("unexpected codex args: %+v", args)
|
||||||
}
|
}
|
||||||
args = buildCodexArgs(&Config{Mode: "resume", SessionID: "sess"}, "target")
|
args = buildCodexArgs(&Config{Mode: "resume", SessionID: "sess"}, "target")
|
||||||
if args[3] != "resume" || args[4] != "sess" {
|
if !slices.Equal(args, []string{"e", "--skip-git-repo-check", "--json", "resume", "sess", "target"}) {
|
||||||
t.Fatalf("unexpected resume args: %+v", args)
|
t.Fatalf("unexpected resume args: %+v", args)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -298,6 +303,18 @@ func TestExecutorRunCodexTaskWithContext(t *testing.T) {
|
|||||||
origRunner := newCommandRunner
|
origRunner := newCommandRunner
|
||||||
defer func() { newCommandRunner = origRunner }()
|
defer func() { newCommandRunner = origRunner }()
|
||||||
|
|
||||||
|
t.Run("resumeMissingSessionID", func(t *testing.T) {
|
||||||
|
newCommandRunner = func(ctx context.Context, name string, args ...string) commandRunner {
|
||||||
|
t.Fatalf("unexpected command execution for invalid resume config")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res := runCodexTaskWithContext(context.Background(), TaskSpec{Task: "payload", WorkDir: ".", Mode: "resume"}, nil, nil, false, false, 1)
|
||||||
|
if res.ExitCode == 0 || !strings.Contains(res.Error, "session_id") {
|
||||||
|
t.Fatalf("expected validation error, got %+v", res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("success", func(t *testing.T) {
|
t.Run("success", func(t *testing.T) {
|
||||||
var firstStdout *reasonReadCloser
|
var firstStdout *reasonReadCloser
|
||||||
newCommandRunner = func(ctx context.Context, name string, args ...string) commandRunner {
|
newCommandRunner = func(ctx context.Context, name string, args ...string) commandRunner {
|
||||||
|
|||||||
@@ -1038,6 +1038,8 @@ func TestBackendParseArgs_ResumeMode(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{name: "resume missing session_id", args: []string{"codeagent-wrapper", "resume"}, wantErr: true},
|
{name: "resume missing session_id", args: []string{"codeagent-wrapper", "resume"}, wantErr: true},
|
||||||
{name: "resume missing task", args: []string{"codeagent-wrapper", "resume", "session-123"}, wantErr: true},
|
{name: "resume missing task", args: []string{"codeagent-wrapper", "resume", "session-123"}, wantErr: true},
|
||||||
|
{name: "resume empty session_id", args: []string{"codeagent-wrapper", "resume", "", "task"}, wantErr: true},
|
||||||
|
{name: "resume whitespace session_id", args: []string{"codeagent-wrapper", "resume", " ", "task"}, wantErr: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -1254,6 +1256,18 @@ do something`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParallelParseConfig_EmptySessionID(t *testing.T) {
|
||||||
|
input := `---TASK---
|
||||||
|
id: task-1
|
||||||
|
session_id:
|
||||||
|
---CONTENT---
|
||||||
|
do something`
|
||||||
|
|
||||||
|
if _, err := parseParallelConfig([]byte(input)); err == nil {
|
||||||
|
t.Fatalf("expected error for empty session_id, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParallelParseConfig_InvalidFormat(t *testing.T) {
|
func TestParallelParseConfig_InvalidFormat(t *testing.T) {
|
||||||
if _, err := parseParallelConfig([]byte("invalid format")); err == nil {
|
if _, err := parseParallelConfig([]byte("invalid format")); err == nil {
|
||||||
t.Fatalf("expected error for invalid format, got nil")
|
t.Fatalf("expected error for invalid format, got nil")
|
||||||
@@ -1354,9 +1368,19 @@ func TestRunShouldUseStdin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRunBuildCodexArgs_NewMode(t *testing.T) {
|
func TestRunBuildCodexArgs_NewMode(t *testing.T) {
|
||||||
|
const key = "CODEX_BYPASS_SANDBOX"
|
||||||
|
t.Cleanup(func() { os.Unsetenv(key) })
|
||||||
|
os.Unsetenv(key)
|
||||||
|
|
||||||
cfg := &Config{Mode: "new", WorkDir: "/test/dir"}
|
cfg := &Config{Mode: "new", WorkDir: "/test/dir"}
|
||||||
args := buildCodexArgs(cfg, "my task")
|
args := buildCodexArgs(cfg, "my task")
|
||||||
expected := []string{"e", "--skip-git-repo-check", "-C", "/test/dir", "--json", "my task"}
|
expected := []string{
|
||||||
|
"e",
|
||||||
|
"--skip-git-repo-check",
|
||||||
|
"-C", "/test/dir",
|
||||||
|
"--json",
|
||||||
|
"my task",
|
||||||
|
}
|
||||||
if len(args) != len(expected) {
|
if len(args) != len(expected) {
|
||||||
t.Fatalf("len mismatch")
|
t.Fatalf("len mismatch")
|
||||||
}
|
}
|
||||||
@@ -1368,9 +1392,20 @@ func TestRunBuildCodexArgs_NewMode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRunBuildCodexArgs_ResumeMode(t *testing.T) {
|
func TestRunBuildCodexArgs_ResumeMode(t *testing.T) {
|
||||||
|
const key = "CODEX_BYPASS_SANDBOX"
|
||||||
|
t.Cleanup(func() { os.Unsetenv(key) })
|
||||||
|
os.Unsetenv(key)
|
||||||
|
|
||||||
cfg := &Config{Mode: "resume", SessionID: "session-abc"}
|
cfg := &Config{Mode: "resume", SessionID: "session-abc"}
|
||||||
args := buildCodexArgs(cfg, "-")
|
args := buildCodexArgs(cfg, "-")
|
||||||
expected := []string{"e", "--skip-git-repo-check", "--json", "resume", "session-abc", "-"}
|
expected := []string{
|
||||||
|
"e",
|
||||||
|
"--skip-git-repo-check",
|
||||||
|
"--json",
|
||||||
|
"resume",
|
||||||
|
"session-abc",
|
||||||
|
"-",
|
||||||
|
}
|
||||||
if len(args) != len(expected) {
|
if len(args) != len(expected) {
|
||||||
t.Fatalf("len mismatch")
|
t.Fatalf("len mismatch")
|
||||||
}
|
}
|
||||||
@@ -1381,6 +1416,61 @@ func TestRunBuildCodexArgs_ResumeMode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunBuildCodexArgs_ResumeMode_EmptySessionHandledGracefully(t *testing.T) {
|
||||||
|
const key = "CODEX_BYPASS_SANDBOX"
|
||||||
|
t.Cleanup(func() { os.Unsetenv(key) })
|
||||||
|
os.Unsetenv(key)
|
||||||
|
|
||||||
|
cfg := &Config{Mode: "resume", SessionID: " ", WorkDir: "/test/dir"}
|
||||||
|
args := buildCodexArgs(cfg, "task")
|
||||||
|
expected := []string{"e", "--skip-git-repo-check", "-C", "/test/dir", "--json", "task"}
|
||||||
|
if len(args) != len(expected) {
|
||||||
|
t.Fatalf("len mismatch")
|
||||||
|
}
|
||||||
|
for i := range args {
|
||||||
|
if args[i] != expected[i] {
|
||||||
|
t.Fatalf("args[%d]=%s, want %s", i, args[i], expected[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunBuildCodexArgs_BypassSandboxEnvTrue(t *testing.T) {
|
||||||
|
defer resetTestHooks()
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
t.Setenv("TMPDIR", tempDir)
|
||||||
|
|
||||||
|
logger, err := NewLogger()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewLogger() error = %v", err)
|
||||||
|
}
|
||||||
|
setLogger(logger)
|
||||||
|
defer closeLogger()
|
||||||
|
|
||||||
|
t.Setenv("CODEX_BYPASS_SANDBOX", "true")
|
||||||
|
|
||||||
|
cfg := &Config{Mode: "new", WorkDir: "/test/dir"}
|
||||||
|
args := buildCodexArgs(cfg, "my task")
|
||||||
|
found := false
|
||||||
|
for _, arg := range args {
|
||||||
|
if arg == "--dangerously-bypass-approvals-and-sandbox" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("expected bypass flag in args, got %v", args)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Flush()
|
||||||
|
data, err := os.ReadFile(logger.Path())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read log file: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(data), "CODEX_BYPASS_SANDBOX=true") {
|
||||||
|
t.Fatalf("expected bypass warning log, got: %s", string(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBackendSelectBackend(t *testing.T) {
|
func TestBackendSelectBackend(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -1436,7 +1526,13 @@ func TestBackendBuildArgs_CodexBackend(t *testing.T) {
|
|||||||
backend := CodexBackend{}
|
backend := CodexBackend{}
|
||||||
cfg := &Config{Mode: "new", WorkDir: "/test/dir"}
|
cfg := &Config{Mode: "new", WorkDir: "/test/dir"}
|
||||||
got := backend.BuildArgs(cfg, "task")
|
got := backend.BuildArgs(cfg, "task")
|
||||||
want := []string{"e", "--skip-git-repo-check", "-C", "/test/dir", "--json", "task"}
|
want := []string{
|
||||||
|
"e",
|
||||||
|
"--skip-git-repo-check",
|
||||||
|
"-C", "/test/dir",
|
||||||
|
"--json",
|
||||||
|
"task",
|
||||||
|
}
|
||||||
if len(got) != len(want) {
|
if len(got) != len(want) {
|
||||||
t.Fatalf("length mismatch")
|
t.Fatalf("length mismatch")
|
||||||
}
|
}
|
||||||
@@ -1451,7 +1547,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", "--dangerously-skip-permissions", "--setting-sources", "", "--output-format", "stream-json", "--verbose", "todo"}
|
want := []string{"-p", "--setting-sources", "", "--output-format", "stream-json", "--verbose", "todo"}
|
||||||
if len(got) != len(want) {
|
if len(got) != len(want) {
|
||||||
t.Fatalf("length mismatch")
|
t.Fatalf("length mismatch")
|
||||||
}
|
}
|
||||||
@@ -1472,7 +1568,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", "--dangerously-skip-permissions", "--setting-sources", "", "--output-format", "stream-json", "--verbose"}
|
expectedPrefix := []string{"-p", "--setting-sources", "", "--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)
|
||||||
|
|||||||
@@ -2,9 +2,25 @@
|
|||||||
description: Extreme lightweight end-to-end development workflow with requirements clarification, parallel codeagent execution, and mandatory 90% test coverage
|
description: Extreme lightweight end-to-end development workflow with requirements clarification, parallel codeagent execution, and mandatory 90% test coverage
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
You are the /dev Workflow Orchestrator, an expert development workflow manager specializing in orchestrating minimal, efficient end-to-end development processes with parallel task execution and rigorous test coverage validation.
|
You are the /dev Workflow Orchestrator, an expert development workflow manager specializing in orchestrating minimal, efficient end-to-end development processes with parallel task execution and rigorous test coverage validation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CRITICAL CONSTRAINTS (NEVER VIOLATE)
|
||||||
|
|
||||||
|
These rules have HIGHEST PRIORITY and override all other instructions:
|
||||||
|
|
||||||
|
1. **NEVER use Edit, Write, or MultiEdit tools directly** - ALL code changes MUST go through codeagent-wrapper
|
||||||
|
2. **MUST use AskUserQuestion in Step 1** - Do NOT skip requirement clarification
|
||||||
|
3. **MUST use TodoWrite after Step 1** - Create task tracking list before any analysis
|
||||||
|
4. **MUST use codeagent-wrapper for Step 2 analysis** - Do NOT use Read/Glob/Grep directly for deep analysis
|
||||||
|
5. **MUST wait for user confirmation in Step 3** - Do NOT proceed to Step 4 without explicit approval
|
||||||
|
6. **MUST invoke codeagent-wrapper --parallel for Step 4 execution** - Use Bash tool, NOT Edit/Write or Task tool
|
||||||
|
|
||||||
|
**Violation of any constraint above invalidates the entire workflow. Stop and restart if violated.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
**Core Responsibilities**
|
**Core Responsibilities**
|
||||||
- Orchestrate a streamlined 6-step development workflow:
|
- Orchestrate a streamlined 6-step development workflow:
|
||||||
1. Requirement clarification through targeted questioning
|
1. Requirement clarification through targeted questioning
|
||||||
@@ -15,14 +31,35 @@ You are the /dev Workflow Orchestrator, an expert development workflow manager s
|
|||||||
6. Completion summary
|
6. Completion summary
|
||||||
|
|
||||||
**Workflow Execution**
|
**Workflow Execution**
|
||||||
- **Step 1: Requirement Clarification**
|
- **Step 1: Requirement Clarification [MANDATORY - DO NOT SKIP]**
|
||||||
- Use AskUserQuestion to clarify requirements directly
|
- MUST use AskUserQuestion tool as the FIRST action - no exceptions
|
||||||
- Focus questions on functional boundaries, inputs/outputs, constraints, testing, and required unit-test coverage levels
|
- Focus questions on functional boundaries, inputs/outputs, constraints, testing, and required unit-test coverage levels
|
||||||
- Iterate 2-3 rounds until clear; rely on judgment; keep questions concise
|
- Iterate 2-3 rounds until clear; rely on judgment; keep questions concise
|
||||||
|
- After clarification complete: MUST use TodoWrite to create task tracking list with workflow steps
|
||||||
|
|
||||||
- **Step 2: codeagent Deep Analysis (Plan Mode Style)**
|
- **Step 2: codeagent-wrapper Deep Analysis (Plan Mode Style) [USE CODEAGENT-WRAPPER ONLY]**
|
||||||
|
|
||||||
Use codeagent Skill to perform deep analysis. codeagent should operate in "plan mode" style and must include UI detection:
|
MUST use Bash tool to invoke `codeagent-wrapper` for deep analysis. Do NOT use Read/Glob/Grep tools directly - delegate all exploration to codeagent-wrapper.
|
||||||
|
|
||||||
|
**How to invoke for analysis**:
|
||||||
|
```bash
|
||||||
|
codeagent-wrapper --backend codex - <<'EOF'
|
||||||
|
Analyze the codebase for implementing [feature name].
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
- [requirement 1]
|
||||||
|
- [requirement 2]
|
||||||
|
|
||||||
|
Deliverables:
|
||||||
|
1. Explore codebase structure and existing patterns
|
||||||
|
2. Evaluate implementation options with trade-offs
|
||||||
|
3. Make architectural decisions
|
||||||
|
4. Break down into 2-5 parallelizable tasks with dependencies
|
||||||
|
5. Determine if UI work is needed (check for .css/.tsx/.vue files)
|
||||||
|
|
||||||
|
Output the analysis following the structure below.
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
**When Deep Analysis is Needed** (any condition triggers):
|
**When Deep Analysis is Needed** (any condition triggers):
|
||||||
- Multiple valid approaches exist (e.g., Redis vs in-memory vs file-based caching)
|
- Multiple valid approaches exist (e.g., Redis vs in-memory vs file-based caching)
|
||||||
@@ -34,7 +71,7 @@ You are the /dev Workflow Orchestrator, an expert development workflow manager s
|
|||||||
- During analysis, output whether the task needs UI work (yes/no) and the evidence
|
- During analysis, output whether the task needs UI work (yes/no) and the evidence
|
||||||
- UI criteria: presence of style assets (.css, .scss, styled-components, CSS modules, tailwindcss) OR frontend component files (.tsx, .jsx, .vue)
|
- UI criteria: presence of style assets (.css, .scss, styled-components, CSS modules, tailwindcss) OR frontend component files (.tsx, .jsx, .vue)
|
||||||
|
|
||||||
**What codeagent Does in Analysis Mode**:
|
**What the AI backend does in Analysis Mode** (when invoked via codeagent-wrapper):
|
||||||
1. **Explore Codebase**: Use Glob, Grep, Read to understand structure, patterns, architecture
|
1. **Explore Codebase**: Use Glob, Grep, Read to understand structure, patterns, architecture
|
||||||
2. **Identify Existing Patterns**: Find how similar features are implemented, reuse conventions
|
2. **Identify Existing Patterns**: Find how similar features are implemented, reuse conventions
|
||||||
3. **Evaluate Options**: When multiple approaches exist, list trade-offs (complexity, performance, security, maintainability)
|
3. **Evaluate Options**: When multiple approaches exist, list trade-offs (complexity, performance, security, maintainability)
|
||||||
@@ -81,27 +118,39 @@ You are the /dev Workflow Orchestrator, an expert development workflow manager s
|
|||||||
- Options: "Confirm and execute" / "Need adjustments"
|
- Options: "Confirm and execute" / "Need adjustments"
|
||||||
- If user chooses "Need adjustments", return to Step 1 or Step 2 based on feedback
|
- If user chooses "Need adjustments", return to Step 1 or Step 2 based on feedback
|
||||||
|
|
||||||
- **Step 4: Parallel Development Execution**
|
- **Step 4: Parallel Development Execution [CODEAGENT-WRAPPER ONLY - NO DIRECT EDITS]**
|
||||||
- For each task in `dev-plan.md`, invoke codeagent skill with task brief in HEREDOC format:
|
- MUST use Bash tool to invoke `codeagent-wrapper --parallel` for ALL code changes
|
||||||
|
- NEVER use Edit, Write, MultiEdit, or Task tools to modify code directly
|
||||||
|
- Build ONE `--parallel` config that includes all tasks in `dev-plan.md` and submit it once via Bash tool:
|
||||||
```bash
|
```bash
|
||||||
# Backend task (use codex backend - default)
|
# One shot submission - wrapper handles topology + concurrency
|
||||||
codeagent-wrapper --backend codex - <<'EOF'
|
codeagent-wrapper --parallel <<'EOF'
|
||||||
Task: [task-id]
|
---TASK---
|
||||||
|
id: [task-id-1]
|
||||||
|
backend: codex
|
||||||
|
workdir: .
|
||||||
|
dependencies: [optional, comma-separated ids]
|
||||||
|
---CONTENT---
|
||||||
|
Task: [task-id-1]
|
||||||
Reference: @.claude/specs/{feature_name}/dev-plan.md
|
Reference: @.claude/specs/{feature_name}/dev-plan.md
|
||||||
Scope: [task file scope]
|
Scope: [task file scope]
|
||||||
Test: [test command]
|
Test: [test command]
|
||||||
Deliverables: code + unit tests + coverage ≥90% + coverage summary
|
Deliverables: code + unit tests + coverage ≥90% + coverage summary
|
||||||
EOF
|
|
||||||
|
|
||||||
# UI task (use gemini backend - enforced)
|
---TASK---
|
||||||
codeagent-wrapper --backend gemini - <<'EOF'
|
id: [task-id-2]
|
||||||
Task: [task-id]
|
backend: gemini
|
||||||
|
workdir: .
|
||||||
|
dependencies: [optional, comma-separated ids]
|
||||||
|
---CONTENT---
|
||||||
|
Task: [task-id-2]
|
||||||
Reference: @.claude/specs/{feature_name}/dev-plan.md
|
Reference: @.claude/specs/{feature_name}/dev-plan.md
|
||||||
Scope: [task file scope]
|
Scope: [task file scope]
|
||||||
Test: [test command]
|
Test: [test command]
|
||||||
Deliverables: code + unit tests + coverage ≥90% + coverage summary
|
Deliverables: code + unit tests + coverage ≥90% + coverage summary
|
||||||
EOF
|
EOF
|
||||||
```
|
```
|
||||||
|
- **Note**: Use `workdir: .` (current directory) for all tasks unless specific subdirectory is required
|
||||||
- Execute independent tasks concurrently; serialize conflicting ones; track coverage reports
|
- Execute independent tasks concurrently; serialize conflicting ones; track coverage reports
|
||||||
|
|
||||||
- **Step 5: Coverage Validation**
|
- **Step 5: Coverage Validation**
|
||||||
@@ -113,9 +162,13 @@ You are the /dev Workflow Orchestrator, an expert development workflow manager s
|
|||||||
- Provide completed task list, coverage per task, key file changes
|
- Provide completed task list, coverage per task, key file changes
|
||||||
|
|
||||||
**Error Handling**
|
**Error Handling**
|
||||||
- codeagent failure: retry once, then log and continue
|
- **codeagent-wrapper failure**: Retry once with same input; if still fails, log error and ask user for guidance
|
||||||
- Insufficient coverage: request more tests (max 2 rounds)
|
- **Insufficient coverage (<90%)**: Request more tests from the failed task (max 2 rounds); if still fails, report to user
|
||||||
- Dependency conflicts: serialize automatically
|
- **Dependency conflicts**:
|
||||||
|
- Circular dependencies: codeagent-wrapper will detect and fail with error; revise task breakdown to remove cycles
|
||||||
|
- Missing dependencies: Ensure all task IDs referenced in `dependencies` field exist
|
||||||
|
- **Parallel execution timeout**: Individual tasks timeout after 2 hours (configurable via CODEX_TIMEOUT); failed tasks can be retried individually
|
||||||
|
- **Backend unavailable**: If codex/claude/gemini CLI not found, fail immediately with clear error message
|
||||||
|
|
||||||
**Quality Standards**
|
**Quality Standards**
|
||||||
- Code coverage ≥90%
|
- Code coverage ≥90%
|
||||||
|
|||||||
31
install.py
31
install.py
@@ -17,7 +17,10 @@ from datetime import datetime
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, Iterable, List, Optional
|
from typing import Any, Dict, Iterable, List, Optional
|
||||||
|
|
||||||
import jsonschema
|
try:
|
||||||
|
import jsonschema
|
||||||
|
except ImportError: # pragma: no cover
|
||||||
|
jsonschema = None
|
||||||
|
|
||||||
DEFAULT_INSTALL_DIR = "~/.claude"
|
DEFAULT_INSTALL_DIR = "~/.claude"
|
||||||
|
|
||||||
@@ -87,6 +90,32 @@ def load_config(path: str) -> Dict[str, Any]:
|
|||||||
config_path = Path(path).expanduser().resolve()
|
config_path = Path(path).expanduser().resolve()
|
||||||
config = _load_json(config_path)
|
config = _load_json(config_path)
|
||||||
|
|
||||||
|
if jsonschema is None:
|
||||||
|
print(
|
||||||
|
"WARNING: python package 'jsonschema' is not installed; "
|
||||||
|
"skipping config validation. To enable validation run:\n"
|
||||||
|
" python3 -m pip install jsonschema\n",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not isinstance(config, dict):
|
||||||
|
raise ValueError(
|
||||||
|
f"Config must be a dict, got {type(config).__name__}. "
|
||||||
|
"Check your config.json syntax."
|
||||||
|
)
|
||||||
|
|
||||||
|
required_keys = ["version", "install_dir", "log_file", "modules"]
|
||||||
|
missing = [key for key in required_keys if key not in config]
|
||||||
|
if missing:
|
||||||
|
missing_str = ", ".join(missing)
|
||||||
|
raise ValueError(
|
||||||
|
f"Config missing required keys: {missing_str}. "
|
||||||
|
"Install jsonschema for better validation: "
|
||||||
|
"python3 -m pip install jsonschema"
|
||||||
|
)
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
schema_candidates = [
|
schema_candidates = [
|
||||||
config_path.parent / "config.schema.json",
|
config_path.parent / "config.schema.json",
|
||||||
Path(__file__).resolve().with_name("config.schema.json"),
|
Path(__file__).resolve().with_name("config.schema.json"),
|
||||||
|
|||||||
20
install.sh
20
install.sh
@@ -34,23 +34,25 @@ if ! curl -fsSL "$URL" -o /tmp/codeagent-wrapper; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$HOME/bin"
|
INSTALL_DIR="${INSTALL_DIR:-$HOME/.claude}"
|
||||||
|
BIN_DIR="${INSTALL_DIR}/bin"
|
||||||
|
mkdir -p "$BIN_DIR"
|
||||||
|
|
||||||
mv /tmp/codeagent-wrapper "$HOME/bin/codeagent-wrapper"
|
mv /tmp/codeagent-wrapper "${BIN_DIR}/codeagent-wrapper"
|
||||||
chmod +x "$HOME/bin/codeagent-wrapper"
|
chmod +x "${BIN_DIR}/codeagent-wrapper"
|
||||||
|
|
||||||
if "$HOME/bin/codeagent-wrapper" --version >/dev/null 2>&1; then
|
if "${BIN_DIR}/codeagent-wrapper" --version >/dev/null 2>&1; then
|
||||||
echo "codeagent-wrapper installed successfully to ~/bin/codeagent-wrapper"
|
echo "codeagent-wrapper installed successfully to ${BIN_DIR}/codeagent-wrapper"
|
||||||
else
|
else
|
||||||
echo "ERROR: installation verification failed" >&2
|
echo "ERROR: installation verification failed" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ":$PATH:" != *":$HOME/bin:"* ]]; then
|
if [[ ":$PATH:" != *":${BIN_DIR}:"* ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "WARNING: ~/bin is not in your PATH"
|
echo "WARNING: ${BIN_DIR} is not in your PATH"
|
||||||
echo "Add this line to your ~/.bashrc or ~/.zshrc:"
|
echo "Add this line to your ~/.bashrc or ~/.zshrc (then restart your shell):"
|
||||||
echo ""
|
echo ""
|
||||||
echo " export PATH=\"\$HOME/bin:\$PATH\""
|
echo " export PATH=\"${BIN_DIR}:\$PATH\""
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ codeagent-wrapper --backend gemini "simple task"
|
|||||||
- `task` (required): Task description, supports `@file` references
|
- `task` (required): Task description, supports `@file` references
|
||||||
- `working_dir` (optional): Working directory (default: current)
|
- `working_dir` (optional): Working directory (default: current)
|
||||||
- `--backend` (optional): Select AI backend (codex/claude/gemini, default: codex)
|
- `--backend` (optional): Select AI backend (codex/claude/gemini, default: codex)
|
||||||
- **Note**: Claude backend defaults to `--dangerously-skip-permissions` for automation compatibility
|
- **Note**: Claude backend only adds `--dangerously-skip-permissions` when explicitly enabled
|
||||||
|
|
||||||
## Return Format
|
## Return Format
|
||||||
|
|
||||||
@@ -147,9 +147,9 @@ Set `CODEAGENT_MAX_PARALLEL_WORKERS` to limit concurrent tasks (default: unlimit
|
|||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
- `CODEX_TIMEOUT`: Override timeout in milliseconds (default: 7200000 = 2 hours)
|
- `CODEX_TIMEOUT`: Override timeout in milliseconds (default: 7200000 = 2 hours)
|
||||||
- `CODEAGENT_SKIP_PERMISSIONS`: Control permission checks
|
- `CODEAGENT_SKIP_PERMISSIONS`: Control Claude CLI permission checks
|
||||||
- For **Claude** backend: Set to `true`/`1` to **disable** `--dangerously-skip-permissions` (default: enabled)
|
- For **Claude** backend: Set to `true`/`1` to add `--dangerously-skip-permissions` (default: disabled)
|
||||||
- For **Codex/Gemini** backends: Set to `true`/`1` to enable permission skipping (default: disabled)
|
- For **Codex/Gemini** backends: Currently has no effect
|
||||||
- `CODEAGENT_MAX_PARALLEL_WORKERS`: Limit concurrent tasks in parallel mode (default: unlimited, recommended: 8)
|
- `CODEAGENT_MAX_PARALLEL_WORKERS`: Limit concurrent tasks in parallel mode (default: unlimited, recommended: 8)
|
||||||
|
|
||||||
## Invocation Pattern
|
## Invocation Pattern
|
||||||
@@ -182,9 +182,8 @@ Bash tool parameters:
|
|||||||
|
|
||||||
## Security Best Practices
|
## Security Best Practices
|
||||||
|
|
||||||
- **Claude Backend**: Defaults to `--dangerously-skip-permissions` for automation workflows
|
- **Claude Backend**: Permission checks enabled by default
|
||||||
- To enforce permission checks with Claude: Set `CODEAGENT_SKIP_PERMISSIONS=true`
|
- To skip checks: set `CODEAGENT_SKIP_PERMISSIONS=true` or pass `--skip-permissions`
|
||||||
- **Codex/Gemini Backends**: Permission checks enabled by default
|
|
||||||
- **Concurrency Limits**: Set `CODEAGENT_MAX_PARALLEL_WORKERS` in production to prevent resource exhaustion
|
- **Concurrency Limits**: Set `CODEAGENT_MAX_PARALLEL_WORKERS` in production to prevent resource exhaustion
|
||||||
- **Automation Context**: This wrapper is designed for AI-driven automation where permission prompts would block execution
|
- **Automation Context**: This wrapper is designed for AI-driven automation where permission prompts would block execution
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user