xone-cli 0.1.0__tar.gz → 0.1.2__tar.gz
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.
- xone_cli-0.1.2/CHANGELOG.md +18 -0
- {xone_cli-0.1.0/src/xone_cli.egg-info → xone_cli-0.1.2}/PKG-INFO +14 -2
- {xone_cli-0.1.0 → xone_cli-0.1.2}/README.md +13 -1
- {xone_cli-0.1.0 → xone_cli-0.1.2}/README.zh-CN.md +13 -1
- {xone_cli-0.1.0 → xone_cli-0.1.2}/docs/feedback/open-source-feedback-ledger.md +1 -1
- xone_cli-0.1.2/docs/install.md +37 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/pyproject.toml +1 -2
- {xone_cli-0.1.0 → xone_cli-0.1.2}/src/xone_cli/__init__.py +1 -2
- {xone_cli-0.1.0 → xone_cli-0.1.2}/src/xone_cli/cli.py +22 -4
- {xone_cli-0.1.0 → xone_cli-0.1.2}/src/xone_cli/evidence.py +7 -3
- xone_cli-0.1.2/src/xone_cli/repo_context.py +67 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/src/xone_cli/tooling.py +60 -1
- {xone_cli-0.1.0 → xone_cli-0.1.2/src/xone_cli.egg-info}/PKG-INFO +14 -2
- {xone_cli-0.1.0 → xone_cli-0.1.2}/src/xone_cli.egg-info/SOURCES.txt +2 -0
- xone_cli-0.1.2/tests/test_cli.py +57 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/tests/test_docs.py +16 -2
- {xone_cli-0.1.0 → xone_cli-0.1.2}/tests/test_evidence.py +13 -0
- xone_cli-0.1.2/tests/test_repo_context.py +45 -0
- xone_cli-0.1.0/CHANGELOG.md +0 -6
- xone_cli-0.1.0/docs/install.md +0 -22
- xone_cli-0.1.0/tests/test_cli.py +0 -34
- {xone_cli-0.1.0 → xone_cli-0.1.2}/LICENSE +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/MANIFEST.in +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/docs/product-foundation.md +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/docs/release.md +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/fixtures/open-source-samples/aider.json +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/fixtures/open-source-samples/anthropics-claude-code.json +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/fixtures/open-source-samples/cline.json +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/fixtures/open-source-samples/continue.json +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/fixtures/open-source-samples/desktop-commander-mcp.json +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/fixtures/open-source-samples/docker-mcp-gateway.json +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/fixtures/open-source-samples/github-github-mcp-server.json +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/fixtures/open-source-samples/microsoft-playwright-mcp.json +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/fixtures/open-source-samples/modelcontextprotocol-servers.json +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/fixtures/open-source-samples/openai-codex.json +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/fixtures/open-source-samples/openhands.json +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/fixtures/open-source-samples/upstash-context7.json +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/ops/constraints/main-entry.md +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/ops/constraints/production.md +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/ops/opt-overlay.md +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/ops/skills/evolution.md +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/setup.cfg +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/src/xone_cli/__main__.py +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/src/xone_cli/lab.py +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/src/xone_cli/model.py +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/src/xone_cli/release.py +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/src/xone_cli/risk.py +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/src/xone_cli/runbook.py +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/src/xone_cli.egg-info/dependency_links.txt +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/src/xone_cli.egg-info/entry_points.txt +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/src/xone_cli.egg-info/requires.txt +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/src/xone_cli.egg-info/top_level.txt +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/tests/test_release.py +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/tests/test_risk_lab.py +0 -0
- {xone_cli-0.1.0 → xone_cli-0.1.2}/tests/test_tooling.py +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.2
|
|
4
|
+
|
|
5
|
+
- Pin `xone doctor --install-plan` to the current recommended X-One tool versions.
|
|
6
|
+
- Add scenario-specific `when` and `next` guidance so first-run users know the next command to try.
|
|
7
|
+
- Keep the unified entrypoint small while making the install plan more actionable.
|
|
8
|
+
|
|
9
|
+
## 0.1.1
|
|
10
|
+
|
|
11
|
+
- Add scenario-based `xone doctor --install-plan` guidance.
|
|
12
|
+
- Let `xone runbook` auto-detect the local repository default branch when `--base` is omitted.
|
|
13
|
+
- Reject remote repository URLs in `xone runbook` with a clone-first message instead of producing a misleading dry run.
|
|
14
|
+
- Expand open-source feedback classifications for adoption evaluation.
|
|
15
|
+
|
|
16
|
+
## 0.1.0
|
|
17
|
+
|
|
18
|
+
- Add the first X-One unified CLI foundation.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xone-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Unified CLI entry point for X-One Agent Evidence Loop workflows.
|
|
5
5
|
Author: X-One-AI
|
|
6
6
|
License-Expression: MIT
|
|
@@ -53,9 +53,14 @@ xone doctor
|
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
55
|
xone doctor
|
|
56
|
-
xone
|
|
56
|
+
xone doctor --install-plan
|
|
57
|
+
xone runbook --head HEAD --dry-run
|
|
57
58
|
```
|
|
58
59
|
|
|
60
|
+
`xone doctor --install-plan` prints scenario-specific install commands with recommended X-One package versions, when to use each scenario, and the next command to try.
|
|
61
|
+
|
|
62
|
+
`xone runbook` auto-detects the local repository default branch. Remote GitHub URLs are not accepted yet; clone the repository first, then pass the local path with `--repo`.
|
|
63
|
+
|
|
59
64
|
`xone-cli` orchestrates these X-One tools:
|
|
60
65
|
|
|
61
66
|
- `agent-pr-evidence`
|
|
@@ -63,6 +68,13 @@ xone runbook --base main --head HEAD --dry-run
|
|
|
63
68
|
- `mcp-risk-index`
|
|
64
69
|
- `ai-incident-lab`
|
|
65
70
|
|
|
71
|
+
Current recommended tool versions:
|
|
72
|
+
|
|
73
|
+
- `agent-pr-evidence 0.4.2`
|
|
74
|
+
- `agent-failure-packet 0.4.2`
|
|
75
|
+
- `mcp-risk-index 0.3.1`
|
|
76
|
+
- `ai-incident-lab 0.2.2`
|
|
77
|
+
|
|
66
78
|
## Boundary
|
|
67
79
|
|
|
68
80
|
- It does not replace the underlying tools.
|
|
@@ -26,9 +26,14 @@ xone doctor
|
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
28
|
xone doctor
|
|
29
|
-
xone
|
|
29
|
+
xone doctor --install-plan
|
|
30
|
+
xone runbook --head HEAD --dry-run
|
|
30
31
|
```
|
|
31
32
|
|
|
33
|
+
`xone doctor --install-plan` prints scenario-specific install commands with recommended X-One package versions, when to use each scenario, and the next command to try.
|
|
34
|
+
|
|
35
|
+
`xone runbook` auto-detects the local repository default branch. Remote GitHub URLs are not accepted yet; clone the repository first, then pass the local path with `--repo`.
|
|
36
|
+
|
|
32
37
|
`xone-cli` orchestrates these X-One tools:
|
|
33
38
|
|
|
34
39
|
- `agent-pr-evidence`
|
|
@@ -36,6 +41,13 @@ xone runbook --base main --head HEAD --dry-run
|
|
|
36
41
|
- `mcp-risk-index`
|
|
37
42
|
- `ai-incident-lab`
|
|
38
43
|
|
|
44
|
+
Current recommended tool versions:
|
|
45
|
+
|
|
46
|
+
- `agent-pr-evidence 0.4.2`
|
|
47
|
+
- `agent-failure-packet 0.4.2`
|
|
48
|
+
- `mcp-risk-index 0.3.1`
|
|
49
|
+
- `ai-incident-lab 0.2.2`
|
|
50
|
+
|
|
39
51
|
## Boundary
|
|
40
52
|
|
|
41
53
|
- It does not replace the underlying tools.
|
|
@@ -26,9 +26,14 @@ xone doctor
|
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
28
|
xone doctor
|
|
29
|
-
xone
|
|
29
|
+
xone doctor --install-plan
|
|
30
|
+
xone runbook --head HEAD --dry-run
|
|
30
31
|
```
|
|
31
32
|
|
|
33
|
+
`xone doctor --install-plan` 会输出按场景分组的安装命令、推荐的 X-One package 版本、适用时机和下一条可尝试的命令。
|
|
34
|
+
|
|
35
|
+
`xone runbook` 会自动探测本地仓库默认分支。当前还不直接接收远程 GitHub URL;请先 clone 仓库,再用 `--repo` 传本地路径。
|
|
36
|
+
|
|
32
37
|
`xone-cli` 编排这些 X-One 工具:
|
|
33
38
|
|
|
34
39
|
- `agent-pr-evidence`
|
|
@@ -36,6 +41,13 @@ xone runbook --base main --head HEAD --dry-run
|
|
|
36
41
|
- `mcp-risk-index`
|
|
37
42
|
- `ai-incident-lab`
|
|
38
43
|
|
|
44
|
+
当前推荐工具版本:
|
|
45
|
+
|
|
46
|
+
- `agent-pr-evidence 0.4.2`
|
|
47
|
+
- `agent-failure-packet 0.4.2`
|
|
48
|
+
- `mcp-risk-index 0.3.1`
|
|
49
|
+
- `ai-incident-lab 0.2.2`
|
|
50
|
+
|
|
39
51
|
## 边界
|
|
40
52
|
|
|
41
53
|
- 不替代底层工具。
|
|
@@ -6,7 +6,7 @@ Rules:
|
|
|
6
6
|
|
|
7
7
|
- Record source URL and observation, not raw sensitive configuration.
|
|
8
8
|
- Do not label third-party projects as safe or unsafe.
|
|
9
|
-
- Classify observations as false-positive, false-negative, adapter-request, scenario-request, or
|
|
9
|
+
- Classify observations as false-positive, false-negative, adapter-request, scenario-request, catalog-update, config-discovery, default-branch, install-friction, agent-instruction-signal, runtime-capability-risk, or cli-ux-improvement.
|
|
10
10
|
|
|
11
11
|
## Samples
|
|
12
12
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Install
|
|
2
|
+
|
|
3
|
+
## Local Development
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
python -m pip install -e '.[dev]'
|
|
7
|
+
xone --version
|
|
8
|
+
xone doctor
|
|
9
|
+
xone doctor --install-plan
|
|
10
|
+
xone runbook --head HEAD --dry-run
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## User Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
python -m pip install xone-cli
|
|
17
|
+
xone doctor
|
|
18
|
+
xone doctor --install-plan
|
|
19
|
+
xone runbook --head HEAD --dry-run
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`xone doctor` reports missing X-One tools and shows install guidance.
|
|
23
|
+
|
|
24
|
+
If `xone doctor` reports missing tools, run `xone doctor --install-plan`. The install plan pins the current recommended X-One package versions and shows the first next command for each scenario.
|
|
25
|
+
|
|
26
|
+
Current scenario groups:
|
|
27
|
+
|
|
28
|
+
- `evidence-loop`: install `agent-pr-evidence 0.4.2`, `agent-failure-packet 0.4.2`, `mcp-risk-index 0.3.1`, and `ai-incident-lab 0.2.2`, then run `xone runbook --head HEAD --dry-run`.
|
|
29
|
+
- `mcp-review`: install `mcp-risk-index 0.3.1`, then run `xone risk context --catalog mcp-risk-index.catalog.yml --output mcp-risk-context.md`.
|
|
30
|
+
- `incident-lab`: install `ai-incident-lab 0.2.2`, then run `xone lab evidence-loop --output agent-evidence-loop.md`.
|
|
31
|
+
|
|
32
|
+
`xone runbook` expects a local git repository. To inspect a public repository, clone it first:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git clone --depth 1 https://github.com/owner/repo
|
|
36
|
+
xone runbook --repo repo --head HEAD --dry-run
|
|
37
|
+
```
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "xone-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "Unified CLI entry point for X-One Agent Evidence Loop workflows."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -49,4 +49,3 @@ where = ["src"]
|
|
|
49
49
|
[tool.pytest.ini_options]
|
|
50
50
|
pythonpath = ["src"]
|
|
51
51
|
testpaths = ["tests"]
|
|
52
|
-
|
|
@@ -12,7 +12,7 @@ from xone_cli.lab import render_evidence_loop_lab
|
|
|
12
12
|
from xone_cli.risk import render_risk_context
|
|
13
13
|
from xone_cli.runbook import write_runbook
|
|
14
14
|
from xone_cli.release import print_release_report, verify_release
|
|
15
|
-
from xone_cli.tooling import doctor_status
|
|
15
|
+
from xone_cli.tooling import doctor_status, install_plan
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def build_parser() -> argparse.ArgumentParser:
|
|
@@ -23,6 +23,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
23
23
|
doctor = subparsers.add_parser("doctor", help="check local X-One tool availability")
|
|
24
24
|
doctor.add_argument("--json", action="store_true", help="write machine-readable status")
|
|
25
25
|
doctor.add_argument("--dry-run", action="store_true", help="show checks without running them")
|
|
26
|
+
doctor.add_argument(
|
|
27
|
+
"--install-plan",
|
|
28
|
+
nargs="?",
|
|
29
|
+
const="all",
|
|
30
|
+
choices=("all", "evidence-loop", "mcp-review", "incident-lab"),
|
|
31
|
+
help="show scenario-based install commands",
|
|
32
|
+
)
|
|
26
33
|
|
|
27
34
|
evidence = subparsers.add_parser("evidence", help="work with PR evidence and failure packets")
|
|
28
35
|
evidence_subparsers = evidence.add_subparsers(dest="evidence_command")
|
|
@@ -65,7 +72,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
65
72
|
verify.add_argument("--json", action="store_true")
|
|
66
73
|
|
|
67
74
|
runbook = subparsers.add_parser("runbook", help="assemble a local X-One evidence runbook")
|
|
68
|
-
_add_evidence_collection_args(runbook)
|
|
75
|
+
_add_evidence_collection_args(runbook, require_base=False)
|
|
69
76
|
runbook.add_argument("--output", type=Path)
|
|
70
77
|
runbook.add_argument("--dry-run", action="store_true", help="show underlying commands without running them")
|
|
71
78
|
runbook.add_argument("--json", action="store_true", help="write machine-readable summary")
|
|
@@ -82,6 +89,9 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
82
89
|
return 0
|
|
83
90
|
|
|
84
91
|
if args.command == "doctor":
|
|
92
|
+
if args.install_plan:
|
|
93
|
+
_print_install_plan(args.install_plan)
|
|
94
|
+
return 0
|
|
85
95
|
report = doctor_status()
|
|
86
96
|
payload = report.to_dict()
|
|
87
97
|
payload["dry_run"] = args.dry_run
|
|
@@ -181,9 +191,17 @@ def _print_doctor(report) -> None:
|
|
|
181
191
|
print(f" install: {tool.install_hint}")
|
|
182
192
|
|
|
183
193
|
|
|
184
|
-
def
|
|
194
|
+
def _print_install_plan(profile: str) -> None:
|
|
195
|
+
print("X-One install plan")
|
|
196
|
+
for item in install_plan(profile):
|
|
197
|
+
print(f"- {item['name']}: {item['command']}")
|
|
198
|
+
print(f" when: {item['when']}")
|
|
199
|
+
print(f" next: {item['next']}")
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _add_evidence_collection_args(parser: argparse.ArgumentParser, *, require_base: bool = True) -> None:
|
|
185
203
|
parser.add_argument("--repo", default=".", help="git repository path")
|
|
186
|
-
parser.add_argument("--base", required=
|
|
204
|
+
parser.add_argument("--base", required=require_base, help="base git ref; runbook can auto-detect when omitted")
|
|
187
205
|
parser.add_argument("--head", required=True, help="head git ref")
|
|
188
206
|
parser.add_argument("--test-log", action="append", default=[], help="test log path; repeatable")
|
|
189
207
|
parser.add_argument("--profile", choices=("default", "strict"), default="strict")
|
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
import tempfile
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
+
from xone_cli.repo_context import detect_default_base, validate_local_repo
|
|
7
8
|
from xone_cli.tooling import run_command
|
|
8
9
|
|
|
9
10
|
|
|
@@ -104,19 +105,23 @@ def build_failure_packet(*, input_path: Path, output: Path, profile: str, dry_ru
|
|
|
104
105
|
def collect_for_runbook(
|
|
105
106
|
*,
|
|
106
107
|
repo: str,
|
|
107
|
-
base: str,
|
|
108
|
+
base: str | None,
|
|
108
109
|
head: str,
|
|
109
110
|
test_logs: list[str],
|
|
110
111
|
profile: str,
|
|
111
112
|
dry_run: bool,
|
|
112
113
|
) -> tuple[int, dict | None, str]:
|
|
114
|
+
valid, validation_message = validate_local_repo(repo)
|
|
115
|
+
if not valid:
|
|
116
|
+
return 2, None, validation_message
|
|
117
|
+
resolved_base = base or detect_default_base(repo)
|
|
113
118
|
command = [
|
|
114
119
|
"agent-pr-evidence",
|
|
115
120
|
"collect",
|
|
116
121
|
"--repo",
|
|
117
122
|
repo,
|
|
118
123
|
"--base",
|
|
119
|
-
|
|
124
|
+
resolved_base,
|
|
120
125
|
"--head",
|
|
121
126
|
head,
|
|
122
127
|
"--profile",
|
|
@@ -135,4 +140,3 @@ def collect_for_runbook(
|
|
|
135
140
|
if result.returncode != 0:
|
|
136
141
|
return result.returncode, None, result.stderr or result.stdout
|
|
137
142
|
return 0, json.loads(output.read_text(encoding="utf-8")), ""
|
|
138
|
-
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def is_remote_repo_url(repo: str) -> bool:
|
|
10
|
+
parsed = urlparse(repo)
|
|
11
|
+
return parsed.scheme in {"http", "https", "ssh", "git"} or bool(re.match(r"^[^/@\s]+@[^:\s]+:.+", repo))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def validate_local_repo(repo: str) -> tuple[bool, str]:
|
|
15
|
+
if is_remote_repo_url(repo):
|
|
16
|
+
return (
|
|
17
|
+
False,
|
|
18
|
+
"Remote repository URLs are not supported by runbook yet. "
|
|
19
|
+
f"Clone first, then pass the local path: git clone --depth 1 {repo}",
|
|
20
|
+
)
|
|
21
|
+
path = Path(repo)
|
|
22
|
+
if not (path / ".git").exists():
|
|
23
|
+
return False, f"{repo} is not a local git repository. Run xone runbook from a git repo or pass --repo /path/to/repo."
|
|
24
|
+
return True, ""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def detect_default_base(repo: str) -> str:
|
|
28
|
+
remote_default = _git_symbolic_ref(repo, "refs/remotes/origin/HEAD")
|
|
29
|
+
if remote_default and remote_default.startswith("origin/"):
|
|
30
|
+
return remote_default.removeprefix("origin/")
|
|
31
|
+
local_default = _git_symbolic_ref(repo, "HEAD")
|
|
32
|
+
if local_default:
|
|
33
|
+
return local_default
|
|
34
|
+
git_dir = Path(repo) / ".git"
|
|
35
|
+
origin_head = _read_ref_name(git_dir / "refs" / "remotes" / "origin" / "HEAD", "refs/remotes/origin/")
|
|
36
|
+
if origin_head:
|
|
37
|
+
return origin_head
|
|
38
|
+
local_head = _read_ref_name(git_dir / "HEAD", "refs/heads/")
|
|
39
|
+
return local_head or "main"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _git_symbolic_ref(repo: str, ref: str) -> str | None:
|
|
43
|
+
try:
|
|
44
|
+
completed = subprocess.run(
|
|
45
|
+
["git", "-C", repo, "symbolic-ref", "--quiet", "--short", ref],
|
|
46
|
+
check=False,
|
|
47
|
+
text=True,
|
|
48
|
+
stdout=subprocess.PIPE,
|
|
49
|
+
stderr=subprocess.PIPE,
|
|
50
|
+
)
|
|
51
|
+
except FileNotFoundError:
|
|
52
|
+
return None
|
|
53
|
+
if completed.returncode != 0:
|
|
54
|
+
return None
|
|
55
|
+
return completed.stdout.strip() or None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _read_ref_name(path: Path, prefix: str) -> str | None:
|
|
59
|
+
if not path.exists():
|
|
60
|
+
return None
|
|
61
|
+
content = path.read_text(encoding="utf-8").strip()
|
|
62
|
+
if not content.startswith("ref: "):
|
|
63
|
+
return None
|
|
64
|
+
ref = content.removeprefix("ref: ").strip()
|
|
65
|
+
if ref.startswith(prefix):
|
|
66
|
+
return ref.removeprefix(prefix)
|
|
67
|
+
return None
|
|
@@ -24,6 +24,40 @@ PACKAGE_BY_TOOL = {
|
|
|
24
24
|
"ai-incident-lab": "xone-ai-incident-lab",
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
RECOMMENDED_VERSION_BY_TOOL = {
|
|
28
|
+
"agent-pr-evidence": "0.4.2",
|
|
29
|
+
"agent-failure-packet": "0.4.2",
|
|
30
|
+
"mcp-risk-index": "0.3.1",
|
|
31
|
+
"ai-incident-lab": "0.2.2",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
INSTALL_PLAN_GROUPS = {
|
|
35
|
+
"evidence-loop": (
|
|
36
|
+
"agent-pr-evidence",
|
|
37
|
+
"agent-failure-packet",
|
|
38
|
+
"mcp-risk-index",
|
|
39
|
+
"ai-incident-lab",
|
|
40
|
+
),
|
|
41
|
+
"mcp-review": ("mcp-risk-index",),
|
|
42
|
+
"incident-lab": ("ai-incident-lab",),
|
|
43
|
+
"all": REQUIRED_TOOLS,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
INSTALL_PLAN_GUIDANCE = {
|
|
47
|
+
"evidence-loop": {
|
|
48
|
+
"when": "review an agent PR, create a redacted failure packet, attach MCP risk context, or run a safe local lab",
|
|
49
|
+
"next": "xone runbook --head HEAD --dry-run",
|
|
50
|
+
},
|
|
51
|
+
"mcp-review": {
|
|
52
|
+
"when": "attach evidence-backed MCP risk context without making an allow/deny decision",
|
|
53
|
+
"next": "xone risk context --catalog mcp-risk-index.catalog.yml --output mcp-risk-context.md",
|
|
54
|
+
},
|
|
55
|
+
"incident-lab": {
|
|
56
|
+
"when": "practice the Agent Evidence Loop with safe-local scenarios",
|
|
57
|
+
"next": "xone lab evidence-loop --output agent-evidence-loop.md",
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
|
|
27
61
|
|
|
28
62
|
def find_tool(name: str) -> str | None:
|
|
29
63
|
return shutil.which(name)
|
|
@@ -31,7 +65,32 @@ def find_tool(name: str) -> str | None:
|
|
|
31
65
|
|
|
32
66
|
def install_hint(name: str) -> str:
|
|
33
67
|
package = PACKAGE_BY_TOOL.get(name, name)
|
|
34
|
-
|
|
68
|
+
version = RECOMMENDED_VERSION_BY_TOOL.get(name)
|
|
69
|
+
pinned = f"{package}=={version}" if version else package
|
|
70
|
+
return f"python -m pip install {pinned}"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def install_plan(profile: str = "all") -> list[dict[str, str]]:
|
|
74
|
+
if profile == "all":
|
|
75
|
+
groups = ("evidence-loop", "mcp-review", "incident-lab")
|
|
76
|
+
else:
|
|
77
|
+
groups = (profile,)
|
|
78
|
+
plan = []
|
|
79
|
+
for group in groups:
|
|
80
|
+
packages = [
|
|
81
|
+
f"{PACKAGE_BY_TOOL[name]}=={RECOMMENDED_VERSION_BY_TOOL[name]}"
|
|
82
|
+
for name in INSTALL_PLAN_GROUPS[group]
|
|
83
|
+
]
|
|
84
|
+
guidance = INSTALL_PLAN_GUIDANCE[group]
|
|
85
|
+
plan.append(
|
|
86
|
+
{
|
|
87
|
+
"name": group,
|
|
88
|
+
"command": f"python -m pip install {' '.join(packages)}",
|
|
89
|
+
"when": guidance["when"],
|
|
90
|
+
"next": guidance["next"],
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
return plan
|
|
35
94
|
|
|
36
95
|
|
|
37
96
|
def run_command(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xone-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Unified CLI entry point for X-One Agent Evidence Loop workflows.
|
|
5
5
|
Author: X-One-AI
|
|
6
6
|
License-Expression: MIT
|
|
@@ -53,9 +53,14 @@ xone doctor
|
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
55
|
xone doctor
|
|
56
|
-
xone
|
|
56
|
+
xone doctor --install-plan
|
|
57
|
+
xone runbook --head HEAD --dry-run
|
|
57
58
|
```
|
|
58
59
|
|
|
60
|
+
`xone doctor --install-plan` prints scenario-specific install commands with recommended X-One package versions, when to use each scenario, and the next command to try.
|
|
61
|
+
|
|
62
|
+
`xone runbook` auto-detects the local repository default branch. Remote GitHub URLs are not accepted yet; clone the repository first, then pass the local path with `--repo`.
|
|
63
|
+
|
|
59
64
|
`xone-cli` orchestrates these X-One tools:
|
|
60
65
|
|
|
61
66
|
- `agent-pr-evidence`
|
|
@@ -63,6 +68,13 @@ xone runbook --base main --head HEAD --dry-run
|
|
|
63
68
|
- `mcp-risk-index`
|
|
64
69
|
- `ai-incident-lab`
|
|
65
70
|
|
|
71
|
+
Current recommended tool versions:
|
|
72
|
+
|
|
73
|
+
- `agent-pr-evidence 0.4.2`
|
|
74
|
+
- `agent-failure-packet 0.4.2`
|
|
75
|
+
- `mcp-risk-index 0.3.1`
|
|
76
|
+
- `ai-incident-lab 0.2.2`
|
|
77
|
+
|
|
66
78
|
## Boundary
|
|
67
79
|
|
|
68
80
|
- It does not replace the underlying tools.
|
|
@@ -31,6 +31,7 @@ src/xone_cli/evidence.py
|
|
|
31
31
|
src/xone_cli/lab.py
|
|
32
32
|
src/xone_cli/model.py
|
|
33
33
|
src/xone_cli/release.py
|
|
34
|
+
src/xone_cli/repo_context.py
|
|
34
35
|
src/xone_cli/risk.py
|
|
35
36
|
src/xone_cli/runbook.py
|
|
36
37
|
src/xone_cli/tooling.py
|
|
@@ -44,5 +45,6 @@ tests/test_cli.py
|
|
|
44
45
|
tests/test_docs.py
|
|
45
46
|
tests/test_evidence.py
|
|
46
47
|
tests/test_release.py
|
|
48
|
+
tests/test_repo_context.py
|
|
47
49
|
tests/test_risk_lab.py
|
|
48
50
|
tests/test_tooling.py
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from xone_cli.cli import main
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _fake_tool(bin_dir: Path, name: str, body: str = "echo tool") -> None:
|
|
8
|
+
path = bin_dir / name
|
|
9
|
+
path.write_text(f"#!/bin/sh\n{body}\n", encoding="utf-8")
|
|
10
|
+
path.chmod(0o755)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_version_outputs_package_version(capsys):
|
|
14
|
+
assert main(["--version"]) == 0
|
|
15
|
+
assert capsys.readouterr().out.strip() == "xone 0.1.2"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_doctor_json_reports_required_tools(tmp_path, monkeypatch, capsys):
|
|
19
|
+
bin_dir = tmp_path / "bin"
|
|
20
|
+
bin_dir.mkdir()
|
|
21
|
+
_fake_tool(bin_dir, "agent-pr-evidence", "echo agent-pr-evidence 0.4.1")
|
|
22
|
+
monkeypatch.setenv("PATH", str(bin_dir))
|
|
23
|
+
|
|
24
|
+
assert main(["doctor", "--json"]) == 0
|
|
25
|
+
payload = json.loads(capsys.readouterr().out)
|
|
26
|
+
assert payload["schema_version"] == "xone.doctor.v1"
|
|
27
|
+
assert payload["tools"][0]["name"] == "agent-pr-evidence"
|
|
28
|
+
assert payload["tools"][0]["available"] is True
|
|
29
|
+
assert payload["tools"][1]["available"] is False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_doctor_install_plan_outputs_scenario_commands(capsys):
|
|
33
|
+
assert main(["doctor", "--install-plan"]) == 0
|
|
34
|
+
|
|
35
|
+
output = capsys.readouterr().out
|
|
36
|
+
assert "X-One install plan" in output
|
|
37
|
+
assert "evidence-loop" in output
|
|
38
|
+
assert "python -m pip install xone-agent-pr-evidence==0.4.2 xone-agent-failure-packet==0.4.2 xone-mcp-risk-index==0.3.1 xone-ai-incident-lab==0.2.2" in output
|
|
39
|
+
assert "when: review an agent PR, create a redacted failure packet, attach MCP risk context, or run a safe local lab" in output
|
|
40
|
+
assert "next: xone runbook --head HEAD --dry-run" in output
|
|
41
|
+
assert "mcp-review" in output
|
|
42
|
+
assert "next: xone risk context --catalog mcp-risk-index.catalog.yml --output mcp-risk-context.md" in output
|
|
43
|
+
assert "incident-lab" in output
|
|
44
|
+
assert "next: xone lab evidence-loop --output agent-evidence-loop.md" in output
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_runbook_dry_run_is_available(capsys):
|
|
48
|
+
assert main(["runbook", "--base", "main", "--head", "HEAD", "--dry-run"]) == 0
|
|
49
|
+
assert "agent-pr-evidence collect" in capsys.readouterr().out
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_runbook_rejects_remote_repo_url_instead_of_dry_run_false_positive(capsys):
|
|
53
|
+
assert main(["runbook", "--repo", "https://github.com/openai/codex", "--head", "HEAD", "--dry-run"]) == 2
|
|
54
|
+
|
|
55
|
+
output = capsys.readouterr().out
|
|
56
|
+
assert "Remote repository URLs are not supported by runbook yet" in output
|
|
57
|
+
assert "git clone --depth 1 https://github.com/openai/codex" in output
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
+
from xone_cli import __version__
|
|
4
|
+
|
|
3
5
|
|
|
4
6
|
def test_docs_and_package_metadata_stay_aligned():
|
|
5
7
|
english = Path("README.md").read_text(encoding="utf-8")
|
|
@@ -7,6 +9,7 @@ def test_docs_and_package_metadata_stay_aligned():
|
|
|
7
9
|
pyproject = Path("pyproject.toml").read_text(encoding="utf-8")
|
|
8
10
|
ci = Path(".github/workflows/ci.yml").read_text(encoding="utf-8")
|
|
9
11
|
publish = Path(".github/workflows/publish.yml").read_text(encoding="utf-8")
|
|
12
|
+
changelog = Path("CHANGELOG.md").read_text(encoding="utf-8")
|
|
10
13
|
production = Path("ops/constraints/production.md").read_text(encoding="utf-8")
|
|
11
14
|
main_entry = Path("ops/constraints/main-entry.md").read_text(encoding="utf-8")
|
|
12
15
|
evolution = Path("ops/skills/evolution.md").read_text(encoding="utf-8")
|
|
@@ -14,16 +17,27 @@ def test_docs_and_package_metadata_stay_aligned():
|
|
|
14
17
|
assert "README.zh-CN.md" in english
|
|
15
18
|
assert "README.md" in chinese
|
|
16
19
|
assert "xone doctor" in english
|
|
17
|
-
assert "xone
|
|
20
|
+
assert "xone doctor --install-plan" in english
|
|
21
|
+
assert "xone runbook --head HEAD --dry-run" in english
|
|
22
|
+
assert "Remote GitHub URLs are not accepted yet" in english
|
|
18
23
|
assert "xone doctor" in chinese
|
|
19
|
-
assert "xone
|
|
24
|
+
assert "xone doctor --install-plan" in chinese
|
|
25
|
+
assert "xone runbook --head HEAD --dry-run" in chinese
|
|
26
|
+
assert "远程 GitHub URL" in chinese
|
|
27
|
+
assert "git clone --depth 1 https://github.com/owner/repo" in Path("docs/install.md").read_text(encoding="utf-8")
|
|
20
28
|
assert "xone release verify --build --install --smoke" in Path("docs/release.md").read_text(encoding="utf-8")
|
|
21
29
|
assert 'name = "xone-cli"' in pyproject
|
|
30
|
+
assert 'version = "0.1.2"' in pyproject
|
|
31
|
+
assert __version__ == "0.1.2"
|
|
32
|
+
assert "## 0.1.2" in changelog
|
|
22
33
|
assert 'xone = "xone_cli.cli:entrypoint"' in pyproject
|
|
23
34
|
assert "python -m pytest -q" in ci
|
|
24
35
|
assert "xone runbook --base HEAD~1 --head HEAD --dry-run" in ci
|
|
25
36
|
assert "environment: testpypi" in publish
|
|
26
37
|
assert "startsWith(github.ref, 'refs/tags/v')" in publish
|
|
38
|
+
assert "doctor --install-plan" in changelog
|
|
39
|
+
assert "recommended X-One tool versions" in changelog
|
|
40
|
+
assert "auto-detect the local repository default branch" in changelog
|
|
27
41
|
assert "Do not make hidden network calls" in production
|
|
28
42
|
assert "Keep top-level commands small" in main_entry
|
|
29
43
|
assert "Delete or weaken" in evolution
|
|
@@ -51,3 +51,16 @@ def test_runbook_dry_run_lists_underlying_command(capsys):
|
|
|
51
51
|
output = capsys.readouterr().out
|
|
52
52
|
assert "agent-pr-evidence collect" in output
|
|
53
53
|
assert "--base main" in output
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_runbook_auto_detects_default_branch_for_local_repo(tmp_path, capsys):
|
|
57
|
+
repo = tmp_path / "repo"
|
|
58
|
+
repo.mkdir()
|
|
59
|
+
(repo / ".git").mkdir()
|
|
60
|
+
(repo / ".git" / "HEAD").write_text("ref: refs/heads/master\n", encoding="utf-8")
|
|
61
|
+
|
|
62
|
+
assert main(["runbook", "--repo", str(repo), "--head", "HEAD", "--dry-run"]) == 0
|
|
63
|
+
|
|
64
|
+
output = capsys.readouterr().out
|
|
65
|
+
assert f"--repo {repo}" in output
|
|
66
|
+
assert "--base master" in output
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from xone_cli.repo_context import detect_default_base, is_remote_repo_url, validate_local_repo
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_detect_default_base_reads_local_git_head(tmp_path):
|
|
7
|
+
repo = tmp_path / "repo"
|
|
8
|
+
repo.mkdir()
|
|
9
|
+
(repo / ".git").mkdir()
|
|
10
|
+
(repo / ".git" / "HEAD").write_text("ref: refs/heads/master\n", encoding="utf-8")
|
|
11
|
+
|
|
12
|
+
assert detect_default_base(str(repo)) == "master"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_detect_default_base_prefers_origin_head_when_available(tmp_path):
|
|
16
|
+
repo = tmp_path / "repo"
|
|
17
|
+
repo.mkdir()
|
|
18
|
+
git = repo / ".git"
|
|
19
|
+
git.mkdir()
|
|
20
|
+
(git / "HEAD").write_text("ref: refs/heads/feature\n", encoding="utf-8")
|
|
21
|
+
(git / "refs" / "remotes" / "origin").mkdir(parents=True)
|
|
22
|
+
(git / "refs" / "remotes" / "origin" / "HEAD").write_text("ref: refs/remotes/origin/trunk\n", encoding="utf-8")
|
|
23
|
+
|
|
24
|
+
assert detect_default_base(str(repo)) == "trunk"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_remote_repo_url_detection():
|
|
28
|
+
assert is_remote_repo_url("https://github.com/openai/codex") is True
|
|
29
|
+
assert is_remote_repo_url("git@github.com:X-One-AI/xone-cli.git") is True
|
|
30
|
+
assert is_remote_repo_url("/tmp/repo") is False
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_validate_local_repo_rejects_remote_url():
|
|
34
|
+
valid, message = validate_local_repo("https://github.com/openai/codex")
|
|
35
|
+
|
|
36
|
+
assert valid is False
|
|
37
|
+
assert "Remote repository URLs are not supported by runbook yet" in message
|
|
38
|
+
assert "git clone --depth 1 https://github.com/openai/codex" in message
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_validate_local_repo_requires_git_metadata(tmp_path):
|
|
42
|
+
valid, message = validate_local_repo(str(tmp_path))
|
|
43
|
+
|
|
44
|
+
assert valid is False
|
|
45
|
+
assert "not a local git repository" in message
|
xone_cli-0.1.0/CHANGELOG.md
DELETED
xone_cli-0.1.0/docs/install.md
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# Install
|
|
2
|
-
|
|
3
|
-
## Local Development
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
python -m pip install -e '.[dev]'
|
|
7
|
-
xone --version
|
|
8
|
-
xone doctor
|
|
9
|
-
xone runbook --base main --head HEAD --dry-run
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
## User Install
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
python -m pip install xone-cli
|
|
16
|
-
xone doctor
|
|
17
|
-
xone runbook --base main --head HEAD --dry-run
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
`xone doctor` reports missing X-One tools and shows install guidance.
|
|
21
|
-
|
|
22
|
-
If `xone doctor` reports missing tools, install the suggested X-One packages and run it again before using `evidence`, `risk`, or `lab` commands.
|
xone_cli-0.1.0/tests/test_cli.py
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
from xone_cli.cli import main
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def _fake_tool(bin_dir: Path, name: str, body: str = "echo tool") -> None:
|
|
8
|
-
path = bin_dir / name
|
|
9
|
-
path.write_text(f"#!/bin/sh\n{body}\n", encoding="utf-8")
|
|
10
|
-
path.chmod(0o755)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def test_version_outputs_package_version(capsys):
|
|
14
|
-
assert main(["--version"]) == 0
|
|
15
|
-
assert capsys.readouterr().out.strip() == "xone 0.1.0"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def test_doctor_json_reports_required_tools(tmp_path, monkeypatch, capsys):
|
|
19
|
-
bin_dir = tmp_path / "bin"
|
|
20
|
-
bin_dir.mkdir()
|
|
21
|
-
_fake_tool(bin_dir, "agent-pr-evidence", "echo agent-pr-evidence 0.4.1")
|
|
22
|
-
monkeypatch.setenv("PATH", str(bin_dir))
|
|
23
|
-
|
|
24
|
-
assert main(["doctor", "--json"]) == 0
|
|
25
|
-
payload = json.loads(capsys.readouterr().out)
|
|
26
|
-
assert payload["schema_version"] == "xone.doctor.v1"
|
|
27
|
-
assert payload["tools"][0]["name"] == "agent-pr-evidence"
|
|
28
|
-
assert payload["tools"][0]["available"] is True
|
|
29
|
-
assert payload["tools"][1]["available"] is False
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def test_runbook_dry_run_is_available(capsys):
|
|
33
|
-
assert main(["runbook", "--base", "main", "--head", "HEAD", "--dry-run"]) == 0
|
|
34
|
-
assert "agent-pr-evidence collect" in capsys.readouterr().out
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xone_cli-0.1.0 → xone_cli-0.1.2}/fixtures/open-source-samples/github-github-mcp-server.json
RENAMED
|
File without changes
|
{xone_cli-0.1.0 → xone_cli-0.1.2}/fixtures/open-source-samples/microsoft-playwright-mcp.json
RENAMED
|
File without changes
|
{xone_cli-0.1.0 → xone_cli-0.1.2}/fixtures/open-source-samples/modelcontextprotocol-servers.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|