@tt-a1i/mco 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -137
- package/README.zh-CN.md +116 -139
- package/package.json +1 -1
- package/runtime/cli.py +4 -5
- package/runtime/config.py +1 -158
package/README.md
CHANGED
|
@@ -1,189 +1,173 @@
|
|
|
1
|
-
# MCO
|
|
1
|
+
# MCO
|
|
2
|
+
|
|
3
|
+
**MCO — One Prompt. Five AI Agents. One Result.**
|
|
2
4
|
|
|
3
5
|
English | [简体中文](./README.zh-CN.md)
|
|
4
6
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
3. [notes.md](./notes.md)
|
|
7
|
+
## What is MCO
|
|
8
|
+
|
|
9
|
+
MCO (Multi-CLI Orchestrator) is a neutral orchestration layer that dispatches a single prompt to multiple AI coding agents in parallel and aggregates their results. No vendor lock-in. No workflow rewrite. Just fan-out, wait-all, and collect.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
1. [capability-probe-spec.md](./capability-probe-spec.md)
|
|
12
|
-
2. [adapter-contract-tests.md](./adapter-contract-tests.md)
|
|
13
|
-
3. [dry-run-plan.md](./dry-run-plan.md)
|
|
14
|
-
4. [implementation-gate-checklist.md](./implementation-gate-checklist.md)
|
|
11
|
+
You keep using Claude Code, Codex CLI, Gemini CLI, OpenCode, and Qwen Code as they are. MCO wires them into a unified execution pipeline with structured output, progress-driven timeouts, and reproducible artifacts.
|
|
15
12
|
|
|
16
|
-
##
|
|
17
|
-
1. [docs/implementation/step0-interface-freeze.md](./docs/implementation/step0-interface-freeze.md)
|
|
18
|
-
2. [docs/contracts/cli-json-v0.1.x.md](./docs/contracts/cli-json-v0.1.x.md)
|
|
19
|
-
3. [docs/contracts/provider-permissions-v0.1.x.md](./docs/contracts/provider-permissions-v0.1.x.md)
|
|
13
|
+
## Key Highlights
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
- **Parallel fan-out** — dispatch to all providers simultaneously, wait-all semantics
|
|
16
|
+
- **Progress-driven timeouts** — agents run freely until completion; cancel only when output goes idle
|
|
17
|
+
- **Dual mode** — `mco review` for structured code review findings, `mco run` for general task execution
|
|
18
|
+
- **Provider-neutral** — uniform adapter contract across 5 CLI tools, no favoring any vendor
|
|
19
|
+
- **Machine-readable output** — JSON result payloads and per-provider artifact trees for downstream automation
|
|
23
20
|
|
|
24
|
-
##
|
|
25
|
-
1. [docs/releases/v0.1.2.md](./docs/releases/v0.1.2.md)
|
|
26
|
-
2. [docs/releases/v0.1.2.zh-CN.md](./docs/releases/v0.1.2.zh-CN.md)
|
|
27
|
-
3. [docs/releases/v0.1.1.md](./docs/releases/v0.1.1.md)
|
|
28
|
-
4. [docs/releases/v0.1.1.zh-CN.md](./docs/releases/v0.1.1.zh-CN.md)
|
|
29
|
-
5. [docs/releases/v0.1.0.md](./docs/releases/v0.1.0.md)
|
|
30
|
-
6. [docs/releases/v0.1.0.zh-CN.md](./docs/releases/v0.1.0.zh-CN.md)
|
|
21
|
+
## Supported Providers
|
|
31
22
|
|
|
32
|
-
|
|
33
|
-
|
|
23
|
+
| Provider | CLI | Status |
|
|
24
|
+
|----------|-----|--------|
|
|
25
|
+
| Claude Code | `claude` | Supported |
|
|
26
|
+
| Codex CLI | `codex` | Supported |
|
|
27
|
+
| Gemini CLI | `gemini` | Supported |
|
|
28
|
+
| OpenCode | `opencode` | Supported |
|
|
29
|
+
| Qwen Code | `qwen` | Supported |
|
|
34
30
|
|
|
35
|
-
|
|
31
|
+
No project migration. No command relearning. No single-tool lock-in.
|
|
36
32
|
|
|
37
|
-
##
|
|
33
|
+
## Quick Start
|
|
38
34
|
|
|
39
|
-
|
|
35
|
+
Install via npm (Python 3 required on PATH):
|
|
40
36
|
|
|
41
37
|
```bash
|
|
42
38
|
npm i -g @tt-a1i/mco
|
|
43
|
-
mco --help
|
|
44
39
|
```
|
|
45
40
|
|
|
46
|
-
|
|
41
|
+
Or install from source:
|
|
47
42
|
|
|
48
43
|
```bash
|
|
49
44
|
git clone https://github.com/tt-a1i/mco.git
|
|
50
45
|
cd mco
|
|
51
46
|
python3 -m pip install -e .
|
|
52
|
-
mco --help
|
|
53
47
|
```
|
|
54
48
|
|
|
55
|
-
|
|
56
|
-
- Not published yet.
|
|
57
|
-
- Publish workflow is ready and will be enabled after PyPI Trusted Publisher setup.
|
|
49
|
+
Run your first multi-agent review:
|
|
58
50
|
|
|
59
|
-
Quick start:
|
|
60
51
|
```bash
|
|
61
|
-
|
|
52
|
+
mco review \
|
|
62
53
|
--repo . \
|
|
63
54
|
--prompt "Review this repository for high-risk bugs and security issues." \
|
|
64
|
-
--providers claude,codex
|
|
55
|
+
--providers claude,codex,qwen
|
|
65
56
|
```
|
|
66
57
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
### Review Mode
|
|
61
|
+
|
|
62
|
+
Structured code review with findings schema. Each provider returns normalized findings with severity, category, evidence, and recommendations.
|
|
71
63
|
|
|
72
|
-
Stdout-only result mode (for caller rendering, no `summary.md/decision.md/findings.json/run.json` write):
|
|
73
64
|
```bash
|
|
74
|
-
|
|
65
|
+
mco review \
|
|
66
|
+
--repo . \
|
|
67
|
+
--prompt "Review for security vulnerabilities and performance issues." \
|
|
68
|
+
--providers claude,codex,gemini,opencode,qwen \
|
|
69
|
+
--json
|
|
75
70
|
```
|
|
76
71
|
|
|
77
|
-
|
|
72
|
+
### Run Mode
|
|
73
|
+
|
|
74
|
+
General-purpose multi-agent execution. No forced output schema — providers complete the task freely.
|
|
75
|
+
|
|
78
76
|
```bash
|
|
79
|
-
|
|
77
|
+
mco run \
|
|
78
|
+
--repo . \
|
|
79
|
+
--prompt "Summarize the architecture of this project." \
|
|
80
|
+
--providers claude,codex \
|
|
81
|
+
--json
|
|
80
82
|
```
|
|
81
83
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
"enforce_findings_contract": false,
|
|
94
|
-
"max_retries": 1,
|
|
95
|
-
"high_escalation_threshold": 1,
|
|
96
|
-
"require_non_empty_findings": true,
|
|
97
|
-
"max_provider_parallelism": 0,
|
|
98
|
-
"allow_paths": [".", "runtime", "scripts"],
|
|
99
|
-
"enforcement_mode": "strict",
|
|
100
|
-
"provider_permissions": {
|
|
101
|
-
"claude": {
|
|
102
|
-
"permission_mode": "plan"
|
|
103
|
-
},
|
|
104
|
-
"codex": {
|
|
105
|
-
"sandbox": "workspace-write"
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
"provider_timeouts": {
|
|
109
|
-
"claude": 300,
|
|
110
|
-
"codex": 240,
|
|
111
|
-
"qwen": 240
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
```
|
|
84
|
+
### Result Modes
|
|
85
|
+
|
|
86
|
+
| Mode | Behavior |
|
|
87
|
+
|------|----------|
|
|
88
|
+
| `--result-mode artifact` | Write artifact files, print summary (default) |
|
|
89
|
+
| `--result-mode stdout` | Print full result to stdout, skip artifact files |
|
|
90
|
+
| `--result-mode both` | Write artifacts and print full result |
|
|
91
|
+
|
|
92
|
+
### Path Constraints
|
|
93
|
+
|
|
94
|
+
Restrict which files agents can access:
|
|
116
95
|
|
|
117
|
-
Run with config:
|
|
118
96
|
```bash
|
|
119
|
-
|
|
97
|
+
mco run \
|
|
98
|
+
--repo . \
|
|
99
|
+
--prompt "Analyze the adapter layer." \
|
|
100
|
+
--providers claude,codex \
|
|
101
|
+
--allow-paths runtime,scripts \
|
|
102
|
+
--target-paths runtime/adapters \
|
|
103
|
+
--enforcement-mode strict
|
|
120
104
|
```
|
|
121
105
|
|
|
122
|
-
|
|
106
|
+
## Defaults and Overrides
|
|
107
|
+
|
|
108
|
+
MCO is zero-config by default. You can run it directly with built-in defaults and override behavior with CLI flags only.
|
|
109
|
+
|
|
110
|
+
### Key Runtime Flags
|
|
111
|
+
|
|
112
|
+
| Flag | Default | Description |
|
|
113
|
+
|------|---------|-------------|
|
|
114
|
+
| `--providers` | `claude,codex` | Comma-separated provider list |
|
|
115
|
+
| `--stall-timeout` | `900` | Cancel when no output progress for this duration |
|
|
116
|
+
| `--review-hard-timeout` | `1800` | Hard deadline for review mode (`0` = disabled) |
|
|
117
|
+
| `--max-provider-parallelism` | `0` | `0` = full parallelism across selected providers |
|
|
118
|
+
| `--enforcement-mode` | `strict` | `strict` fails closed on unmet permissions |
|
|
119
|
+
| `--provider-timeouts` | unset | Per-provider stall-timeout overrides (`provider=seconds`) |
|
|
120
|
+
| `--provider-permissions-json` | unset | Provider permission mapping JSON |
|
|
121
|
+
|
|
122
|
+
Example:
|
|
123
|
+
|
|
123
124
|
```bash
|
|
124
|
-
|
|
125
|
+
mco review \
|
|
125
126
|
--repo . \
|
|
126
|
-
--prompt "Review for bugs
|
|
127
|
-
--providers claude,codex,
|
|
128
|
-
--strict-contract \
|
|
129
|
-
--max-provider-parallelism 2 \
|
|
127
|
+
--prompt "Review for bugs." \
|
|
128
|
+
--providers claude,codex,qwen \
|
|
130
129
|
--stall-timeout 900 \
|
|
131
130
|
--review-hard-timeout 1800 \
|
|
131
|
+
--max-provider-parallelism 0 \
|
|
132
132
|
--provider-timeouts qwen=900,codex=900
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
Run
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
--json
|
|
135
|
+
Run `mco review --help` for the full flag list.
|
|
136
|
+
|
|
137
|
+
## How It Works
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
prompt ─> MCO ─┬─> Claude Code ─┐
|
|
141
|
+
├─> Codex CLI ├─> aggregate ─> artifacts + JSON
|
|
142
|
+
├─> Gemini CLI │
|
|
143
|
+
├─> OpenCode │
|
|
144
|
+
└─> Qwen Code ──┘
|
|
146
145
|
```
|
|
147
146
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
Notes:
|
|
162
|
-
- YAML config requires `pyyaml` installed; otherwise use JSON config.
|
|
163
|
-
- Review prompt is wrapped with a JSON finding contract, but strict parse enforcement is optional.
|
|
164
|
-
- Enable strict gate behavior with `--strict-contract` (or `policy.enforce_findings_contract=true` in config).
|
|
165
|
-
- `run` mode does not force findings schema; it focuses on execution aggregation and provider success.
|
|
166
|
-
- `result_mode=artifact` (default): write user-facing artifacts and print compact result.
|
|
167
|
-
- `result_mode=stdout`: print provider-level result payload to stdout, skip user-facing artifact files.
|
|
168
|
-
- `result_mode=both`: write artifacts and print provider-level payload.
|
|
169
|
-
- Execution model is `wait-all`: one provider timeout/failure does not stop others.
|
|
170
|
-
- Timeout behavior is progress-driven:
|
|
171
|
-
- `stall_timeout_seconds`: cancel only when output progress is idle beyond threshold.
|
|
172
|
-
- `review_hard_timeout_seconds`: hard deadline applied only in `review` mode.
|
|
173
|
-
- `max_provider_parallelism=0` (or omitted) means full parallelism across selected providers.
|
|
174
|
-
- `provider_timeouts` are provider-specific stall-timeout overrides.
|
|
175
|
-
- `allow_paths` and `target_paths` are validated against `repo_root`; path escape is rejected.
|
|
176
|
-
- `enforcement_mode=strict` (default) fails closed when provider permission requirements cannot be honored.
|
|
177
|
-
|
|
178
|
-
## Step5 Benchmark Script
|
|
179
|
-
Use this script to generate serial vs full-parallel evidence and write reports under `reports/adapter-contract/<date>/`:
|
|
147
|
+
Each provider runs as an independent subprocess through a uniform adapter contract:
|
|
148
|
+
|
|
149
|
+
1. **Detect** — check binary presence and auth status
|
|
150
|
+
2. **Run** — spawn CLI process with prompt, capture stdout/stderr
|
|
151
|
+
3. **Poll** — monitor process + output byte growth for progress detection
|
|
152
|
+
4. **Cancel** — SIGTERM/SIGKILL on stall timeout or hard deadline
|
|
153
|
+
5. **Normalize** — extract structured findings from raw output
|
|
154
|
+
|
|
155
|
+
Execution model is **wait-all**: one provider's timeout or failure never stops others.
|
|
156
|
+
|
|
157
|
+
## Artifacts
|
|
158
|
+
|
|
159
|
+
Each run produces a structured artifact tree:
|
|
180
160
|
|
|
181
|
-
```bash
|
|
182
|
-
./scripts/run_step5_parallel_benchmark.sh
|
|
183
161
|
```
|
|
162
|
+
reports/review/<task_id>/
|
|
163
|
+
summary.md # Human-readable summary
|
|
164
|
+
decision.md # PASS / FAIL / ESCALATE / PARTIAL
|
|
165
|
+
findings.json # Aggregated normalized findings (review mode)
|
|
166
|
+
run.json # Machine-readable execution metadata
|
|
167
|
+
providers/ # Per-provider result JSON
|
|
168
|
+
raw/ # Raw stdout/stderr logs
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## License
|
|
184
172
|
|
|
185
|
-
|
|
186
|
-
- `providers_total`
|
|
187
|
-
- `parse_success_rate`
|
|
188
|
-
- `effective_findings_count`
|
|
189
|
-
- `zero_finding_provider_count`
|
|
173
|
+
UNLICENSED
|
package/README.zh-CN.md
CHANGED
|
@@ -1,196 +1,173 @@
|
|
|
1
|
-
# MCO
|
|
1
|
+
# MCO
|
|
2
|
+
|
|
3
|
+
**MCO — 一条提示词,五个 AI Agent,一份结果。**
|
|
2
4
|
|
|
3
5
|
[English](./README.md) | 简体中文
|
|
4
6
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
## MCO 是什么
|
|
8
|
+
|
|
9
|
+
MCO(Multi-CLI Orchestrator)是一个中立的编排层,将单条提示词并行分发给多个 AI 编程 Agent,汇总执行结果。不绑定任何厂商,不改变你的工作流。Fan-out、Wait-all、Collect。
|
|
10
|
+
|
|
11
|
+
你继续照常使用 Claude Code、Codex CLI、Gemini CLI、OpenCode、Qwen Code。MCO 负责把它们串联成统一的执行管线,提供结构化输出、进度驱动超时、可复现的产物。
|
|
9
12
|
|
|
10
|
-
##
|
|
11
|
-
1. [capability-probe-spec.md](./capability-probe-spec.md)
|
|
12
|
-
2. [adapter-contract-tests.md](./adapter-contract-tests.md)
|
|
13
|
-
3. [dry-run-plan.md](./dry-run-plan.md)
|
|
14
|
-
4. [implementation-gate-checklist.md](./implementation-gate-checklist.md)
|
|
13
|
+
## 核心特性
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
- **并行扇出** — 同时分发到所有 provider,wait-all 语义
|
|
16
|
+
- **进度驱动超时** — agent 自由跑完,仅在长时间无输出时取消
|
|
17
|
+
- **双模式** — `mco review` 结构化代码审查,`mco run` 通用任务执行
|
|
18
|
+
- **厂商中立** — 5 个 CLI 工具统一适配器契约,不偏向任何厂商
|
|
19
|
+
- **机器可读输出** — JSON 结果 + 每个 provider 独立产物树,便于下游自动化
|
|
20
20
|
|
|
21
|
-
##
|
|
22
|
-
1. [task_plan.md](./task_plan.md)
|
|
21
|
+
## 支持的 Provider
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
| Provider | CLI | 状态 |
|
|
24
|
+
|----------|-----|------|
|
|
25
|
+
| Claude Code | `claude` | 已支持 |
|
|
26
|
+
| Codex CLI | `codex` | 已支持 |
|
|
27
|
+
| Gemini CLI | `gemini` | 已支持 |
|
|
28
|
+
| OpenCode | `opencode` | 已支持 |
|
|
29
|
+
| Qwen Code | `qwen` | 已支持 |
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
`mco review`:统一的审查入口。
|
|
34
|
-
`mco run`:通用任务执行入口(不强制 findings schema)。
|
|
31
|
+
无需迁移项目,无需重学命令,无需绑定单一工具。
|
|
35
32
|
|
|
36
|
-
##
|
|
33
|
+
## 快速开始
|
|
37
34
|
|
|
38
|
-
npm
|
|
35
|
+
通过 npm 安装(需要系统有 Python 3):
|
|
39
36
|
|
|
40
37
|
```bash
|
|
41
38
|
npm i -g @tt-a1i/mco
|
|
42
|
-
mco --help
|
|
43
39
|
```
|
|
44
40
|
|
|
45
|
-
|
|
41
|
+
或从源码安装:
|
|
46
42
|
|
|
47
43
|
```bash
|
|
48
44
|
git clone https://github.com/tt-a1i/mco.git
|
|
49
45
|
cd mco
|
|
50
46
|
python3 -m pip install -e .
|
|
51
|
-
mco --help
|
|
52
47
|
```
|
|
53
48
|
|
|
54
|
-
|
|
55
|
-
- 目前尚未发布。
|
|
56
|
-
- 发布流程已就绪,待完成 PyPI Trusted Publisher 配置后开启。
|
|
57
|
-
|
|
58
|
-
快速开始:
|
|
49
|
+
运行第一次多 Agent 审查:
|
|
59
50
|
|
|
60
51
|
```bash
|
|
61
|
-
|
|
52
|
+
mco review \
|
|
62
53
|
--repo . \
|
|
63
54
|
--prompt "Review this repository for high-risk bugs and security issues." \
|
|
64
|
-
--providers claude,codex
|
|
55
|
+
--providers claude,codex,qwen
|
|
65
56
|
```
|
|
66
57
|
|
|
67
|
-
|
|
58
|
+
## 使用方式
|
|
68
59
|
|
|
69
|
-
|
|
70
|
-
./mco review --repo . --prompt "Review for bugs." --providers claude,codex --json
|
|
71
|
-
```
|
|
60
|
+
### Review 模式
|
|
72
61
|
|
|
73
|
-
|
|
62
|
+
结构化代码审查,输出标准化的 findings(含严重级别、分类、证据、建议)。
|
|
74
63
|
|
|
75
64
|
```bash
|
|
76
|
-
|
|
65
|
+
mco review \
|
|
66
|
+
--repo . \
|
|
67
|
+
--prompt "Review for security vulnerabilities and performance issues." \
|
|
68
|
+
--providers claude,codex,gemini,opencode,qwen \
|
|
69
|
+
--json
|
|
77
70
|
```
|
|
78
71
|
|
|
79
|
-
|
|
72
|
+
### Run 模式
|
|
73
|
+
|
|
74
|
+
通用多 Agent 任务执行,不强制输出格式,provider 自由完成任务。
|
|
80
75
|
|
|
81
76
|
```bash
|
|
82
|
-
|
|
77
|
+
mco run \
|
|
78
|
+
--repo . \
|
|
79
|
+
--prompt "Summarize the architecture of this project." \
|
|
80
|
+
--providers claude,codex \
|
|
81
|
+
--json
|
|
83
82
|
```
|
|
84
83
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
"policy": {
|
|
93
|
-
"timeout_seconds": 180,
|
|
94
|
-
"stall_timeout_seconds": 900,
|
|
95
|
-
"poll_interval_seconds": 1.0,
|
|
96
|
-
"review_hard_timeout_seconds": 1800,
|
|
97
|
-
"enforce_findings_contract": false,
|
|
98
|
-
"max_retries": 1,
|
|
99
|
-
"high_escalation_threshold": 1,
|
|
100
|
-
"require_non_empty_findings": true,
|
|
101
|
-
"max_provider_parallelism": 0,
|
|
102
|
-
"allow_paths": [".", "runtime", "scripts"],
|
|
103
|
-
"enforcement_mode": "strict",
|
|
104
|
-
"provider_permissions": {
|
|
105
|
-
"claude": {
|
|
106
|
-
"permission_mode": "plan"
|
|
107
|
-
},
|
|
108
|
-
"codex": {
|
|
109
|
-
"sandbox": "workspace-write"
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
"provider_timeouts": {
|
|
113
|
-
"claude": 300,
|
|
114
|
-
"codex": 240,
|
|
115
|
-
"qwen": 240
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
```
|
|
84
|
+
### 结果模式
|
|
85
|
+
|
|
86
|
+
| 模式 | 行为 |
|
|
87
|
+
|------|------|
|
|
88
|
+
| `--result-mode artifact` | 写产物文件,输出摘要(默认) |
|
|
89
|
+
| `--result-mode stdout` | 完整结果输出到 stdout,不写产物文件 |
|
|
90
|
+
| `--result-mode both` | 既写产物又输出完整结果 |
|
|
120
91
|
|
|
121
|
-
|
|
92
|
+
### 路径约束
|
|
93
|
+
|
|
94
|
+
限制 agent 可访问的文件范围:
|
|
122
95
|
|
|
123
96
|
```bash
|
|
124
|
-
|
|
97
|
+
mco run \
|
|
98
|
+
--repo . \
|
|
99
|
+
--prompt "Analyze the adapter layer." \
|
|
100
|
+
--providers claude,codex \
|
|
101
|
+
--allow-paths runtime,scripts \
|
|
102
|
+
--target-paths runtime/adapters \
|
|
103
|
+
--enforcement-mode strict
|
|
125
104
|
```
|
|
126
105
|
|
|
127
|
-
|
|
106
|
+
## 默认值与参数覆盖
|
|
107
|
+
|
|
108
|
+
MCO 默认零配置可用。直接运行即可,按需通过命令行参数覆盖行为。
|
|
109
|
+
|
|
110
|
+
### 关键运行参数
|
|
111
|
+
|
|
112
|
+
| 参数 | 默认值 | 说明 |
|
|
113
|
+
|------|--------|------|
|
|
114
|
+
| `--providers` | `claude,codex` | 逗号分隔 provider 列表 |
|
|
115
|
+
| `--stall-timeout` | `900` | 无输出进展超过此时间才取消 |
|
|
116
|
+
| `--review-hard-timeout` | `1800` | review 模式硬截止(`0` = 禁用) |
|
|
117
|
+
| `--max-provider-parallelism` | `0` | `0` = 选中 provider 全并行 |
|
|
118
|
+
| `--enforcement-mode` | `strict` | 权限不满足时 fail-closed |
|
|
119
|
+
| `--provider-timeouts` | 未设置 | provider 级 stall timeout 覆盖(`provider=seconds`) |
|
|
120
|
+
| `--provider-permissions-json` | 未设置 | provider 权限映射 JSON |
|
|
121
|
+
|
|
122
|
+
示例:
|
|
128
123
|
|
|
129
124
|
```bash
|
|
130
|
-
|
|
125
|
+
mco review \
|
|
131
126
|
--repo . \
|
|
132
|
-
--prompt "Review for bugs
|
|
133
|
-
--providers claude,codex,
|
|
134
|
-
--strict-contract \
|
|
135
|
-
--max-provider-parallelism 2 \
|
|
127
|
+
--prompt "Review for bugs." \
|
|
128
|
+
--providers claude,codex,qwen \
|
|
136
129
|
--stall-timeout 900 \
|
|
137
130
|
--review-hard-timeout 1800 \
|
|
131
|
+
--max-provider-parallelism 0 \
|
|
138
132
|
--provider-timeouts qwen=900,codex=900
|
|
139
133
|
```
|
|
140
134
|
|
|
141
|
-
|
|
135
|
+
运行 `mco review --help` 查看完整参数列表。
|
|
142
136
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
--provider-permissions-json '{"codex":{"sandbox":"workspace-write"},"claude":{"permission_mode":"plan"}}' \
|
|
152
|
-
--json
|
|
137
|
+
## 工作原理
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
prompt ─> MCO ─┬─> Claude Code ─┐
|
|
141
|
+
├─> Codex CLI ├─> 聚合 ─> 产物 + JSON
|
|
142
|
+
├─> Gemini CLI │
|
|
143
|
+
├─> OpenCode │
|
|
144
|
+
└─> Qwen Code ──┘
|
|
153
145
|
```
|
|
154
146
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
说明:
|
|
169
|
-
- YAML 配置需要 `pyyaml`;否则使用 JSON 配置。
|
|
170
|
-
- review 提示词会附加 JSON finding 合同;是否强制由策略控制。
|
|
171
|
-
- 可用 `--strict-contract`(或配置 `policy.enforce_findings_contract=true`)开启严格合同。
|
|
172
|
-
- `run` 模式不强制 findings schema,聚焦执行与聚合。
|
|
173
|
-
- `result_mode=artifact`(默认):写产物并输出简报。
|
|
174
|
-
- `result_mode=stdout`:输出 provider 结果,不写用户侧产物。
|
|
175
|
-
- `result_mode=both`:既写产物又输出 provider 结果。
|
|
176
|
-
- 执行模型为 `wait-all`:单 provider 失败/超时不会中断其它 provider。
|
|
177
|
-
- 超时是进度驱动:
|
|
178
|
-
- `stall_timeout_seconds`:仅在长时间无输出进展时取消。
|
|
179
|
-
- `review_hard_timeout_seconds`:仅 `review` 模式硬截止。
|
|
180
|
-
- `max_provider_parallelism=0`(或省略)表示全并行。
|
|
181
|
-
- `provider_timeouts` 为 provider 级 stall-timeout 覆盖项。
|
|
182
|
-
- `allow_paths` 与 `target_paths` 会对 `repo_root` 做越界校验。
|
|
183
|
-
- `enforcement_mode=strict`(默认)下,权限要求不满足会 fail-closed。
|
|
184
|
-
|
|
185
|
-
## Step5 性能脚本
|
|
186
|
-
用于产出串行 vs 全并行对比报告(写入 `reports/adapter-contract/<date>/`):
|
|
147
|
+
每个 provider 通过统一的适配器契约作为独立子进程运行:
|
|
148
|
+
|
|
149
|
+
1. **Detect** — 检测二进制文件和认证状态
|
|
150
|
+
2. **Run** — 启动 CLI 进程,传入提示词,捕获 stdout/stderr
|
|
151
|
+
3. **Poll** — 监控进程状态 + 输出字节增长,判断活跃度
|
|
152
|
+
4. **Cancel** — stall timeout 或硬截止时 SIGTERM/SIGKILL
|
|
153
|
+
5. **Normalize** — 从原始输出中提取结构化 findings
|
|
154
|
+
|
|
155
|
+
执行模型是 **wait-all**:单个 provider 超时或失败不会中断其他 provider。
|
|
156
|
+
|
|
157
|
+
## 产物结构
|
|
158
|
+
|
|
159
|
+
每次执行生成结构化产物树:
|
|
187
160
|
|
|
188
|
-
```bash
|
|
189
|
-
./scripts/run_step5_parallel_benchmark.sh
|
|
190
161
|
```
|
|
162
|
+
reports/review/<task_id>/
|
|
163
|
+
summary.md # 人类可读摘要
|
|
164
|
+
decision.md # PASS / FAIL / ESCALATE / PARTIAL
|
|
165
|
+
findings.json # 聚合后的标准化 findings(review 模式)
|
|
166
|
+
run.json # 机器可读执行元数据
|
|
167
|
+
providers/ # 各 provider 结果 JSON
|
|
168
|
+
raw/ # 原始 stdout/stderr 日志
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## 许可证
|
|
191
172
|
|
|
192
|
-
|
|
193
|
-
- `providers_total`
|
|
194
|
-
- `parse_success_rate`
|
|
195
|
-
- `effective_findings_count`
|
|
196
|
-
- `zero_finding_provider_count`
|
|
173
|
+
UNLICENSED
|
package/package.json
CHANGED
package/runtime/cli.py
CHANGED
|
@@ -6,7 +6,7 @@ import sys
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Dict, List
|
|
8
8
|
|
|
9
|
-
from .config import ReviewConfig, ReviewPolicy
|
|
9
|
+
from .config import ReviewConfig, ReviewPolicy
|
|
10
10
|
from .review_engine import ReviewRequest, run_review
|
|
11
11
|
|
|
12
12
|
|
|
@@ -141,7 +141,6 @@ def _add_common_execution_args(parser: argparse.ArgumentParser) -> None:
|
|
|
141
141
|
parser.add_argument("--repo", default=".", help="Repository root path")
|
|
142
142
|
parser.add_argument("--prompt", required=True, help="Task prompt")
|
|
143
143
|
parser.add_argument("--providers", default="", help="Comma-separated providers, e.g. claude,codex")
|
|
144
|
-
parser.add_argument("--config", default="", help="Config file path (.json or .yaml/.yml)")
|
|
145
144
|
parser.add_argument("--artifact-base", default="", help="Artifact base directory override")
|
|
146
145
|
parser.add_argument("--state-file", default="", help="Runtime state file override")
|
|
147
146
|
parser.add_argument("--task-id", default="", help="Optional stable task id")
|
|
@@ -215,7 +214,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
215
214
|
|
|
216
215
|
|
|
217
216
|
def _resolve_config(args: argparse.Namespace) -> ReviewConfig:
|
|
218
|
-
cfg =
|
|
217
|
+
cfg = ReviewConfig()
|
|
219
218
|
providers = _parse_providers(args.providers) if args.providers else cfg.providers
|
|
220
219
|
artifact_base = args.artifact_base or cfg.artifact_base
|
|
221
220
|
state_file = args.state_file or cfg.state_file
|
|
@@ -239,7 +238,7 @@ def _resolve_config(args: argparse.Namespace) -> ReviewConfig:
|
|
|
239
238
|
review_hard_timeout_seconds = cfg.policy.review_hard_timeout_seconds
|
|
240
239
|
if args.review_hard_timeout is not None and args.review_hard_timeout >= 0:
|
|
241
240
|
review_hard_timeout_seconds = args.review_hard_timeout
|
|
242
|
-
enforce_findings_contract =
|
|
241
|
+
enforce_findings_contract = bool(args.strict_contract)
|
|
243
242
|
|
|
244
243
|
policy = ReviewPolicy(
|
|
245
244
|
timeout_seconds=cfg.policy.timeout_seconds,
|
|
@@ -268,7 +267,7 @@ def main(argv: List[str] | None = None) -> int:
|
|
|
268
267
|
|
|
269
268
|
try:
|
|
270
269
|
cfg = _resolve_config(args)
|
|
271
|
-
except
|
|
270
|
+
except ValueError as exc:
|
|
272
271
|
print(f"Configuration error: {exc}", file=sys.stderr)
|
|
273
272
|
return 2
|
|
274
273
|
repo_root = str(Path(args.repo).resolve())
|
package/runtime/config.py
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
from dataclasses import dataclass, field
|
|
5
|
-
from
|
|
6
|
-
from typing import Any, Dict, List, Optional
|
|
4
|
+
from typing import Dict, List
|
|
7
5
|
|
|
8
6
|
DEFAULT_PROVIDER_TIMEOUTS: Dict[str, int] = {
|
|
9
7
|
}
|
|
@@ -32,158 +30,3 @@ class ReviewConfig:
|
|
|
32
30
|
artifact_base: str = "reports/review"
|
|
33
31
|
state_file: str = ".mco/state.json"
|
|
34
32
|
policy: ReviewPolicy = field(default_factory=ReviewPolicy)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _as_bool(value: Any, default: bool) -> bool:
|
|
38
|
-
if isinstance(value, bool):
|
|
39
|
-
return value
|
|
40
|
-
if isinstance(value, str):
|
|
41
|
-
lowered = value.strip().lower()
|
|
42
|
-
if lowered in ("true", "1", "yes", "y", "on"):
|
|
43
|
-
return True
|
|
44
|
-
if lowered in ("false", "0", "no", "n", "off"):
|
|
45
|
-
return False
|
|
46
|
-
return default
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def _to_policy(payload: Dict[str, Any]) -> ReviewPolicy:
|
|
50
|
-
raw_provider_timeouts = payload.get("provider_timeouts", {})
|
|
51
|
-
provider_timeouts: Dict[str, int] = dict(DEFAULT_PROVIDER_TIMEOUTS)
|
|
52
|
-
if isinstance(raw_provider_timeouts, dict):
|
|
53
|
-
for key, value in raw_provider_timeouts.items():
|
|
54
|
-
provider = str(key).strip()
|
|
55
|
-
if not provider:
|
|
56
|
-
continue
|
|
57
|
-
try:
|
|
58
|
-
timeout = int(value)
|
|
59
|
-
except Exception:
|
|
60
|
-
continue
|
|
61
|
-
if timeout <= 0:
|
|
62
|
-
continue
|
|
63
|
-
provider_timeouts[provider] = timeout
|
|
64
|
-
|
|
65
|
-
try:
|
|
66
|
-
max_parallel = int(payload.get("max_provider_parallelism", 0))
|
|
67
|
-
except Exception:
|
|
68
|
-
max_parallel = 0
|
|
69
|
-
if max_parallel < 0:
|
|
70
|
-
max_parallel = 0
|
|
71
|
-
|
|
72
|
-
raw_allow_paths = payload.get("allow_paths", ["."])
|
|
73
|
-
allow_paths: List[str]
|
|
74
|
-
if isinstance(raw_allow_paths, str):
|
|
75
|
-
allow_paths = [item.strip() for item in raw_allow_paths.split(",") if item.strip()]
|
|
76
|
-
elif isinstance(raw_allow_paths, list):
|
|
77
|
-
allow_paths = [str(item).strip() for item in raw_allow_paths if str(item).strip()]
|
|
78
|
-
else:
|
|
79
|
-
allow_paths = ["."]
|
|
80
|
-
if not allow_paths:
|
|
81
|
-
allow_paths = ["."]
|
|
82
|
-
|
|
83
|
-
raw_provider_permissions = payload.get("provider_permissions", {})
|
|
84
|
-
provider_permissions: Dict[str, Dict[str, str]] = {}
|
|
85
|
-
if isinstance(raw_provider_permissions, dict):
|
|
86
|
-
for provider, permissions in raw_provider_permissions.items():
|
|
87
|
-
provider_name = str(provider).strip()
|
|
88
|
-
if not provider_name or not isinstance(permissions, dict):
|
|
89
|
-
continue
|
|
90
|
-
normalized: Dict[str, str] = {}
|
|
91
|
-
for key, value in permissions.items():
|
|
92
|
-
key_name = str(key).strip()
|
|
93
|
-
if not key_name:
|
|
94
|
-
continue
|
|
95
|
-
normalized[key_name] = str(value)
|
|
96
|
-
if normalized:
|
|
97
|
-
provider_permissions[provider_name] = normalized
|
|
98
|
-
|
|
99
|
-
enforcement_mode = str(payload.get("enforcement_mode", "strict")).strip().lower()
|
|
100
|
-
if enforcement_mode not in ("strict", "best_effort"):
|
|
101
|
-
enforcement_mode = "strict"
|
|
102
|
-
|
|
103
|
-
try:
|
|
104
|
-
stall_timeout_seconds = int(payload.get("stall_timeout_seconds", 900))
|
|
105
|
-
except Exception:
|
|
106
|
-
stall_timeout_seconds = 900
|
|
107
|
-
if stall_timeout_seconds <= 0:
|
|
108
|
-
stall_timeout_seconds = 900
|
|
109
|
-
|
|
110
|
-
try:
|
|
111
|
-
poll_interval_seconds = float(payload.get("poll_interval_seconds", 1.0))
|
|
112
|
-
except Exception:
|
|
113
|
-
poll_interval_seconds = 1.0
|
|
114
|
-
if poll_interval_seconds <= 0:
|
|
115
|
-
poll_interval_seconds = 1.0
|
|
116
|
-
|
|
117
|
-
try:
|
|
118
|
-
review_hard_timeout_seconds = int(payload.get("review_hard_timeout_seconds", 1800))
|
|
119
|
-
except Exception:
|
|
120
|
-
review_hard_timeout_seconds = 1800
|
|
121
|
-
if review_hard_timeout_seconds < 0:
|
|
122
|
-
review_hard_timeout_seconds = 1800
|
|
123
|
-
|
|
124
|
-
return ReviewPolicy(
|
|
125
|
-
timeout_seconds=int(payload.get("timeout_seconds", 180)),
|
|
126
|
-
stall_timeout_seconds=stall_timeout_seconds,
|
|
127
|
-
poll_interval_seconds=poll_interval_seconds,
|
|
128
|
-
review_hard_timeout_seconds=review_hard_timeout_seconds,
|
|
129
|
-
enforce_findings_contract=_as_bool(payload.get("enforce_findings_contract", False), False),
|
|
130
|
-
max_retries=int(payload.get("max_retries", 1)),
|
|
131
|
-
high_escalation_threshold=int(payload.get("high_escalation_threshold", 1)),
|
|
132
|
-
require_non_empty_findings=_as_bool(payload.get("require_non_empty_findings", True), True),
|
|
133
|
-
max_provider_parallelism=max_parallel,
|
|
134
|
-
provider_timeouts=provider_timeouts,
|
|
135
|
-
allow_paths=allow_paths,
|
|
136
|
-
provider_permissions=provider_permissions,
|
|
137
|
-
enforcement_mode=enforcement_mode,
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def _normalize_payload(payload: Dict[str, Any]) -> ReviewConfig:
|
|
142
|
-
policy_payload = payload.get("policy", {})
|
|
143
|
-
if not isinstance(policy_payload, dict):
|
|
144
|
-
policy_payload = {}
|
|
145
|
-
providers = payload.get("providers", ["claude", "codex"])
|
|
146
|
-
if isinstance(providers, str):
|
|
147
|
-
providers = [item.strip() for item in providers.split(",") if item.strip()]
|
|
148
|
-
if not isinstance(providers, list):
|
|
149
|
-
providers = ["claude", "codex"]
|
|
150
|
-
providers = [str(item).strip() for item in providers if str(item).strip()]
|
|
151
|
-
if not providers:
|
|
152
|
-
providers = ["claude", "codex"]
|
|
153
|
-
|
|
154
|
-
return ReviewConfig(
|
|
155
|
-
providers=providers,
|
|
156
|
-
artifact_base=str(payload.get("artifact_base", "reports/review")),
|
|
157
|
-
state_file=str(payload.get("state_file", ".mco/state.json")),
|
|
158
|
-
policy=_to_policy(policy_payload),
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def load_review_config(config_path: Optional[str]) -> ReviewConfig:
|
|
163
|
-
if not config_path:
|
|
164
|
-
return ReviewConfig()
|
|
165
|
-
path = Path(config_path)
|
|
166
|
-
if not path.exists():
|
|
167
|
-
raise FileNotFoundError(f"config file not found: {config_path}")
|
|
168
|
-
|
|
169
|
-
suffix = path.suffix.lower()
|
|
170
|
-
raw_text = path.read_text(encoding="utf-8")
|
|
171
|
-
if suffix == ".json":
|
|
172
|
-
payload = json.loads(raw_text)
|
|
173
|
-
if not isinstance(payload, dict):
|
|
174
|
-
raise ValueError("config root must be an object")
|
|
175
|
-
return _normalize_payload(payload)
|
|
176
|
-
|
|
177
|
-
if suffix in (".yaml", ".yml"):
|
|
178
|
-
try:
|
|
179
|
-
import yaml # type: ignore
|
|
180
|
-
except Exception as exc:
|
|
181
|
-
raise RuntimeError(
|
|
182
|
-
"YAML config requires pyyaml. Install with: pip install pyyaml, or use a .json config."
|
|
183
|
-
) from exc
|
|
184
|
-
payload = yaml.safe_load(raw_text)
|
|
185
|
-
if not isinstance(payload, dict):
|
|
186
|
-
raise ValueError("config root must be a map")
|
|
187
|
-
return _normalize_payload(payload)
|
|
188
|
-
|
|
189
|
-
raise ValueError(f"unsupported config format: {config_path}")
|