@tt-a1i/mco 0.1.2

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 ADDED
@@ -0,0 +1,190 @@
1
+ # MCO Docs Index
2
+
3
+ ## Read First
4
+ 1. [multi-cli-orchestrator-proposal.md](./multi-cli-orchestrator-proposal.md)
5
+ 2. [capability-research.md](./capability-research.md)
6
+ 3. [notes.md](./notes.md)
7
+
8
+ ## Gate Artifacts
9
+ 1. [capability-probe-spec.md](./capability-probe-spec.md)
10
+ 2. [adapter-contract-tests.md](./adapter-contract-tests.md)
11
+ 3. [dry-run-plan.md](./dry-run-plan.md)
12
+ 4. [implementation-gate-checklist.md](./implementation-gate-checklist.md)
13
+
14
+ ## Implementation Freeze
15
+ 1. [docs/implementation/step0-interface-freeze.md](./docs/implementation/step0-interface-freeze.md)
16
+ 2. [docs/contracts/cli-json-v0.1.x.md](./docs/contracts/cli-json-v0.1.x.md)
17
+ 3. [docs/contracts/provider-permissions-v0.1.x.md](./docs/contracts/provider-permissions-v0.1.x.md)
18
+
19
+ ## Planning and Tracking
20
+ 1. [task_plan.md](./task_plan.md)
21
+
22
+ ## Release Notes
23
+ 1. [docs/releases/v0.1.2.md](./docs/releases/v0.1.2.md)
24
+ 2. [docs/releases/v0.1.2.zh-CN.md](./docs/releases/v0.1.2.zh-CN.md)
25
+ 3. [docs/releases/v0.1.1.md](./docs/releases/v0.1.1.md)
26
+ 4. [docs/releases/v0.1.1.zh-CN.md](./docs/releases/v0.1.1.zh-CN.md)
27
+ 5. [docs/releases/v0.1.0.md](./docs/releases/v0.1.0.md)
28
+ 6. [docs/releases/v0.1.0.zh-CN.md](./docs/releases/v0.1.0.zh-CN.md)
29
+
30
+ ## Unified CLI (Step 2)
31
+ `mco review` is the unified entrypoint for running a review task.
32
+
33
+ `mco run` is the generalized execution entrypoint for agent-style task orchestration (no forced findings schema).
34
+
35
+ ## Installation
36
+
37
+ Python package (recommended):
38
+
39
+ ```bash
40
+ pipx install mco
41
+ mco --help
42
+ ```
43
+
44
+ Install from source (editable):
45
+
46
+ ```bash
47
+ git clone https://github.com/tt-a1i/mco.git
48
+ cd mco
49
+ python3 -m pip install -e .
50
+ mco --help
51
+ ```
52
+
53
+ NPM wrapper (Python 3 required on PATH):
54
+
55
+ ```bash
56
+ npm i -g @tt-a1i/mco
57
+ mco --help
58
+ ```
59
+
60
+ Quick start:
61
+ ```bash
62
+ ./mco review \
63
+ --repo . \
64
+ --prompt "Review this repository for high-risk bugs and security issues." \
65
+ --providers claude,codex
66
+ ```
67
+
68
+ Machine-readable output:
69
+ ```bash
70
+ ./mco review --repo . --prompt "Review for bugs." --providers claude,codex --json
71
+ ```
72
+
73
+ Stdout-only result mode (for caller rendering, no `summary.md/decision.md/findings.json/run.json` write):
74
+ ```bash
75
+ ./mco review --repo . --prompt "Review for bugs." --providers claude,codex --result-mode stdout --json
76
+ ```
77
+
78
+ General run mode:
79
+ ```bash
80
+ ./mco run --repo . --prompt "Summarize the current repo architecture." --providers claude,codex --json
81
+ ```
82
+
83
+ Config file (JSON):
84
+ ```json
85
+ {
86
+ "providers": ["claude", "codex"],
87
+ "artifact_base": "reports/review",
88
+ "state_file": ".mco/state.json",
89
+ "policy": {
90
+ "timeout_seconds": 180,
91
+ "stall_timeout_seconds": 900,
92
+ "poll_interval_seconds": 1.0,
93
+ "review_hard_timeout_seconds": 1800,
94
+ "enforce_findings_contract": false,
95
+ "max_retries": 1,
96
+ "high_escalation_threshold": 1,
97
+ "require_non_empty_findings": true,
98
+ "max_provider_parallelism": 0,
99
+ "allow_paths": [".", "runtime", "scripts"],
100
+ "enforcement_mode": "strict",
101
+ "provider_permissions": {
102
+ "claude": {
103
+ "permission_mode": "plan"
104
+ },
105
+ "codex": {
106
+ "sandbox": "workspace-write"
107
+ }
108
+ },
109
+ "provider_timeouts": {
110
+ "claude": 300,
111
+ "codex": 240,
112
+ "qwen": 240
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ Run with config:
119
+ ```bash
120
+ ./mco review --config ./mco.example.json --repo . --prompt "Review for bugs and security issues."
121
+ ```
122
+
123
+ Override fan-out and per-provider timeout from CLI:
124
+ ```bash
125
+ ./mco review \
126
+ --repo . \
127
+ --prompt "Review for bugs and security issues." \
128
+ --providers claude,codex,gemini,opencode,qwen \
129
+ --strict-contract \
130
+ --max-provider-parallelism 2 \
131
+ --stall-timeout 900 \
132
+ --review-hard-timeout 1800 \
133
+ --provider-timeouts qwen=900,codex=900
134
+ ```
135
+
136
+ Run mode with hard path constraints:
137
+ ```bash
138
+ ./mco run \
139
+ --repo . \
140
+ --prompt "Compare adapter behaviors and return a short markdown summary." \
141
+ --providers claude,codex \
142
+ --allow-paths runtime,scripts \
143
+ --target-paths runtime/adapters,runtime/review_engine.py \
144
+ --enforcement-mode strict \
145
+ --provider-permissions-json '{"codex":{"sandbox":"workspace-write"},"claude":{"permission_mode":"plan"}}' \
146
+ --json
147
+ ```
148
+
149
+ Artifacts are written to:
150
+ - `<artifact_base>/<task_id>/summary.md`
151
+ - `<artifact_base>/<task_id>/decision.md`
152
+ - `<artifact_base>/<task_id>/findings.json`
153
+ - `<artifact_base>/<task_id>/run.json`
154
+ - `<artifact_base>/<task_id>/providers/*.json`
155
+ - `<artifact_base>/<task_id>/raw/*.log`
156
+
157
+ `run.json` includes audit fields for reproducibility:
158
+ - `effective_cwd`
159
+ - `allow_paths_hash`
160
+ - `permissions_hash`
161
+
162
+ Notes:
163
+ - YAML config requires `pyyaml` installed; otherwise use JSON config.
164
+ - Review prompt is wrapped with a JSON finding contract, but strict parse enforcement is optional.
165
+ - Enable strict gate behavior with `--strict-contract` (or `policy.enforce_findings_contract=true` in config).
166
+ - `run` mode does not force findings schema; it focuses on execution aggregation and provider success.
167
+ - `result_mode=artifact` (default): write user-facing artifacts and print compact result.
168
+ - `result_mode=stdout`: print provider-level result payload to stdout, skip user-facing artifact files.
169
+ - `result_mode=both`: write artifacts and print provider-level payload.
170
+ - Execution model is `wait-all`: one provider timeout/failure does not stop others.
171
+ - Timeout behavior is progress-driven:
172
+ - `stall_timeout_seconds`: cancel only when output progress is idle beyond threshold.
173
+ - `review_hard_timeout_seconds`: hard deadline applied only in `review` mode.
174
+ - `max_provider_parallelism=0` (or omitted) means full parallelism across selected providers.
175
+ - `provider_timeouts` are provider-specific stall-timeout overrides.
176
+ - `allow_paths` and `target_paths` are validated against `repo_root`; path escape is rejected.
177
+ - `enforcement_mode=strict` (default) fails closed when provider permission requirements cannot be honored.
178
+
179
+ ## Step5 Benchmark Script
180
+ Use this script to generate serial vs full-parallel evidence and write reports under `reports/adapter-contract/<date>/`:
181
+
182
+ ```bash
183
+ ./scripts/run_step5_parallel_benchmark.sh
184
+ ```
185
+
186
+ The generated summary JSON includes separated parse-vs-findings metrics:
187
+ - `providers_total`
188
+ - `parse_success_rate`
189
+ - `effective_findings_count`
190
+ - `zero_finding_provider_count`
package/bin/mco.js ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawnSync } = require("node:child_process");
4
+ const { resolve } = require("node:path");
5
+
6
+ const scriptPath = resolve(__dirname, "..", "mco");
7
+ const args = process.argv.slice(2);
8
+
9
+ const result = spawnSync("python3", [scriptPath, ...args], {
10
+ stdio: "inherit",
11
+ });
12
+
13
+ if (result.error) {
14
+ console.error(`Failed to run python3: ${result.error.message}`);
15
+ process.exit(1);
16
+ }
17
+
18
+ process.exit(result.status === null ? 1 : result.status);
19
+
package/mco ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ ROOT_DIR = Path(__file__).resolve().parent
8
+ if str(ROOT_DIR) not in sys.path:
9
+ sys.path.insert(0, str(ROOT_DIR))
10
+
11
+ from runtime.cli import main
12
+
13
+
14
+ if __name__ == "__main__":
15
+ raise SystemExit(main())
16
+
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@tt-a1i/mco",
3
+ "version": "0.1.2",
4
+ "description": "Node wrapper for the mco CLI (Python runtime required).",
5
+ "license": "UNLICENSED",
6
+ "bin": {
7
+ "mco": "bin/mco.js"
8
+ },
9
+ "type": "commonjs",
10
+ "engines": {
11
+ "node": ">=18"
12
+ },
13
+ "scripts": {
14
+ "test": "node -e \"console.log('No JS tests. Use Python test suite.')\""
15
+ },
16
+ "files": [
17
+ "bin/mco.js",
18
+ "mco",
19
+ "runtime"
20
+ ]
21
+ }
@@ -0,0 +1,8 @@
1
+ """Runtime package for orchestrator gate implementation."""
2
+
3
+ from .artifacts import ARTIFACT_LAYOUT_VERSION
4
+ from .types import RUN_RESULT_SCHEMA_VERSION
5
+
6
+ __version__ = "0.1.2"
7
+
8
+ __all__ = ["ARTIFACT_LAYOUT_VERSION", "RUN_RESULT_SCHEMA_VERSION", "__version__"]
@@ -0,0 +1,7 @@
1
+ from .claude import ClaudeAdapter
2
+ from .codex import CodexAdapter
3
+ from .gemini import GeminiAdapter
4
+ from .opencode import OpenCodeAdapter
5
+ from .qwen import QwenAdapter
6
+
7
+ __all__ = ["ClaudeAdapter", "CodexAdapter", "GeminiAdapter", "OpenCodeAdapter", "QwenAdapter"]
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, List
4
+
5
+ from ..contracts import CapabilitySet, NormalizeContext, NormalizedFinding, TaskInput
6
+ from .parsing import normalize_findings_from_text
7
+ from .shim import ShimAdapterBase
8
+
9
+
10
+ class ClaudeAdapter(ShimAdapterBase):
11
+ def __init__(self) -> None:
12
+ super().__init__(
13
+ provider_id="claude",
14
+ binary_name="claude",
15
+ capability_set=CapabilitySet(
16
+ tiers=["C0", "C1", "C2", "C3", "C4", "C5", "C6"],
17
+ supports_native_async=False,
18
+ supports_poll_endpoint=False,
19
+ supports_resume_after_restart=True,
20
+ supports_schema_enforcement=True,
21
+ min_supported_version="2.1.59",
22
+ tested_os=["macos"],
23
+ ),
24
+ )
25
+
26
+ def _auth_check_command(self, binary: str) -> List[str]:
27
+ return [binary, "auth", "status"]
28
+
29
+ def supported_permission_keys(self) -> List[str]:
30
+ return ["permission_mode"]
31
+
32
+ def _build_command(self, input_task: TaskInput) -> List[str]:
33
+ permission_mode = "plan"
34
+ raw_permissions = input_task.metadata.get("provider_permissions")
35
+ if isinstance(raw_permissions, dict):
36
+ value = raw_permissions.get("permission_mode")
37
+ if isinstance(value, str) and value.strip():
38
+ permission_mode = value.strip()
39
+ return [
40
+ "claude",
41
+ "-p",
42
+ "--permission-mode",
43
+ permission_mode,
44
+ "--output-format",
45
+ "text",
46
+ input_task.prompt,
47
+ ]
48
+
49
+ def _build_command_for_record(self) -> List[str]:
50
+ return ["claude", "-p", "--permission-mode", "plan", "--output-format", "text", "<prompt>"]
51
+
52
+ def _is_success(self, return_code: int, stdout_text: str, stderr_text: str) -> bool:
53
+ if return_code != 0:
54
+ return False
55
+ text = f"{stdout_text}\n{stderr_text}".lower()
56
+ return "api error" not in text
57
+
58
+ def normalize(self, raw: Any, ctx: NormalizeContext) -> List[NormalizedFinding]:
59
+ text = raw if isinstance(raw, str) else ""
60
+ return normalize_findings_from_text(text, ctx, "claude")
@@ -0,0 +1,84 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, List
4
+
5
+ from ..contracts import CapabilitySet, NormalizeContext, NormalizedFinding, TaskInput
6
+ from .parsing import normalize_findings_from_text
7
+ from .shim import ShimAdapterBase
8
+
9
+
10
+ class CodexAdapter(ShimAdapterBase):
11
+ def __init__(self) -> None:
12
+ super().__init__(
13
+ provider_id="codex",
14
+ binary_name="codex",
15
+ capability_set=CapabilitySet(
16
+ tiers=["C0", "C1", "C2", "C3", "C4", "C5"],
17
+ supports_native_async=False,
18
+ supports_poll_endpoint=False,
19
+ supports_resume_after_restart=True,
20
+ supports_schema_enforcement=True,
21
+ min_supported_version="0.46.0",
22
+ tested_os=["macos"],
23
+ ),
24
+ )
25
+
26
+ def _auth_check_command(self, binary: str) -> List[str]:
27
+ return [binary, "login", "status"]
28
+
29
+ def supported_permission_keys(self) -> List[str]:
30
+ return ["sandbox"]
31
+
32
+ def _build_command(self, input_task: TaskInput) -> List[str]:
33
+ sandbox = "workspace-write"
34
+ raw_permissions = input_task.metadata.get("provider_permissions")
35
+ if isinstance(raw_permissions, dict):
36
+ value = raw_permissions.get("sandbox")
37
+ if isinstance(value, str) and value.strip():
38
+ sandbox = value.strip()
39
+ cmd = [
40
+ "codex",
41
+ "exec",
42
+ "--skip-git-repo-check",
43
+ "-C",
44
+ input_task.repo_root,
45
+ "--sandbox",
46
+ sandbox,
47
+ "--json",
48
+ ]
49
+ output_schema_path = input_task.metadata.get("output_schema_path")
50
+ if isinstance(output_schema_path, str) and output_schema_path.strip():
51
+ cmd.extend(["--output-schema", output_schema_path.strip()])
52
+ cmd.append(input_task.prompt)
53
+ return cmd
54
+
55
+ def _build_command_for_record(self) -> List[str]:
56
+ return [
57
+ "codex",
58
+ "exec",
59
+ "--skip-git-repo-check",
60
+ "-C",
61
+ "<repo_root>",
62
+ "--sandbox",
63
+ "workspace-write",
64
+ "--json",
65
+ "--output-schema",
66
+ "<schema-path>",
67
+ "<prompt>",
68
+ ]
69
+
70
+ def _is_success(self, return_code: int, stdout_text: str, stderr_text: str) -> bool:
71
+ if return_code == 0:
72
+ return True
73
+ # Codex may emit MCP startup errors and still return useful JSON events.
74
+ if stdout_text.strip() and "\"type\":\"turn.completed\"" in stdout_text:
75
+ return True
76
+ if stdout_text.strip() and "\"ok\":true" in stdout_text:
77
+ return True
78
+ if "mcp client" in stderr_text.lower() and stdout_text.strip():
79
+ return True
80
+ return False
81
+
82
+ def normalize(self, raw: Any, ctx: NormalizeContext) -> List[NormalizedFinding]:
83
+ text = raw if isinstance(raw, str) else ""
84
+ return normalize_findings_from_text(text, ctx, "codex")
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, List
4
+
5
+ from ..contracts import CapabilitySet, NormalizeContext, NormalizedFinding, TaskInput
6
+ from .parsing import normalize_findings_from_text
7
+ from .shim import ShimAdapterBase
8
+
9
+
10
+ class GeminiAdapter(ShimAdapterBase):
11
+ def __init__(self) -> None:
12
+ super().__init__(
13
+ provider_id="gemini",
14
+ binary_name="gemini",
15
+ capability_set=CapabilitySet(
16
+ tiers=["C0", "C1", "C2", "C3"],
17
+ supports_native_async=False,
18
+ supports_poll_endpoint=False,
19
+ supports_resume_after_restart=False,
20
+ supports_schema_enforcement=False,
21
+ min_supported_version="0.1.7",
22
+ tested_os=["macos"],
23
+ ),
24
+ )
25
+
26
+ def _auth_check_command(self, binary: str) -> List[str]:
27
+ return [binary, "-p", "Reply with exactly OK"]
28
+
29
+ def _build_command(self, input_task: TaskInput) -> List[str]:
30
+ return ["gemini", "-p", input_task.prompt]
31
+
32
+ def _build_command_for_record(self) -> List[str]:
33
+ return ["gemini", "-p", "<prompt>"]
34
+
35
+ def _is_success(self, return_code: int, stdout_text: str, stderr_text: str) -> bool:
36
+ if return_code != 0:
37
+ return False
38
+ text = f"{stdout_text}\n{stderr_text}".lower()
39
+ if "unknown arguments" in text:
40
+ return False
41
+ if "api error" in text:
42
+ return False
43
+ return True
44
+
45
+ def normalize(self, raw: Any, ctx: NormalizeContext) -> List[NormalizedFinding]:
46
+ text = raw if isinstance(raw, str) else ""
47
+ return normalize_findings_from_text(text, ctx, "gemini")
48
+
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, List
4
+
5
+ from ..contracts import CapabilitySet, NormalizeContext, NormalizedFinding, TaskInput
6
+ from .parsing import normalize_findings_from_text
7
+ from .shim import ShimAdapterBase
8
+
9
+
10
+ class OpenCodeAdapter(ShimAdapterBase):
11
+ def __init__(self) -> None:
12
+ super().__init__(
13
+ provider_id="opencode",
14
+ binary_name="opencode",
15
+ capability_set=CapabilitySet(
16
+ tiers=["C0", "C1", "C2", "C3", "C4"],
17
+ supports_native_async=True,
18
+ supports_poll_endpoint=True,
19
+ supports_resume_after_restart=True,
20
+ supports_schema_enforcement=False,
21
+ min_supported_version="1.2.11",
22
+ tested_os=["macos"],
23
+ ),
24
+ )
25
+
26
+ def _auth_check_command(self, binary: str) -> List[str]:
27
+ return [binary, "auth", "list"]
28
+
29
+ def _build_command(self, input_task: TaskInput) -> List[str]:
30
+ return ["opencode", "run", input_task.prompt, "--format", "json"]
31
+
32
+ def _build_command_for_record(self) -> List[str]:
33
+ return ["opencode", "run", "<prompt>", "--format", "json"]
34
+
35
+ def normalize(self, raw: Any, ctx: NormalizeContext) -> List[NormalizedFinding]:
36
+ text = raw if isinstance(raw, str) else ""
37
+ return normalize_findings_from_text(text, ctx, "opencode")
38
+