cli-bridge-mcp 0.1.0__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.
- cli_bridge_mcp-0.1.0/.github/CODE_OF_CONDUCT.md +29 -0
- cli_bridge_mcp-0.1.0/.github/CONTRIBUTING.md +49 -0
- cli_bridge_mcp-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +26 -0
- cli_bridge_mcp-0.1.0/.github/ISSUE_TEMPLATE/cli_flag_broken.md +26 -0
- cli_bridge_mcp-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- cli_bridge_mcp-0.1.0/.github/ISSUE_TEMPLATE/lane_request.md +30 -0
- cli_bridge_mcp-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +17 -0
- cli_bridge_mcp-0.1.0/.github/SECURITY.md +73 -0
- cli_bridge_mcp-0.1.0/.github/workflows/drift-check.yml +47 -0
- cli_bridge_mcp-0.1.0/.github/workflows/pages.yml +35 -0
- cli_bridge_mcp-0.1.0/.github/workflows/release.yml +47 -0
- cli_bridge_mcp-0.1.0/.github/workflows/tests.yml +92 -0
- cli_bridge_mcp-0.1.0/.gitignore +30 -0
- cli_bridge_mcp-0.1.0/.gitleaks.toml +14 -0
- cli_bridge_mcp-0.1.0/AGENTS.md +124 -0
- cli_bridge_mcp-0.1.0/CHANGELOG.md +526 -0
- cli_bridge_mcp-0.1.0/CLAUDE.md +1 -0
- cli_bridge_mcp-0.1.0/LICENSE +21 -0
- cli_bridge_mcp-0.1.0/PKG-INFO +437 -0
- cli_bridge_mcp-0.1.0/README.md +420 -0
- cli_bridge_mcp-0.1.0/assets/banner-dark.svg +1 -0
- cli_bridge_mcp-0.1.0/assets/banner-light.svg +1 -0
- cli_bridge_mcp-0.1.0/assets/banner.gif +0 -0
- cli_bridge_mcp-0.1.0/assets/demo-borrow.gif +0 -0
- cli_bridge_mcp-0.1.0/assets/demo.gif +0 -0
- cli_bridge_mcp-0.1.0/assets/mark-dark.svg +1 -0
- cli_bridge_mcp-0.1.0/assets/mark-light.svg +1 -0
- cli_bridge_mcp-0.1.0/assets/mark.gif +0 -0
- cli_bridge_mcp-0.1.0/assets/social-card.png +0 -0
- cli_bridge_mcp-0.1.0/assets/social-dark.svg +1 -0
- cli_bridge_mcp-0.1.0/assets/social-light.svg +1 -0
- cli_bridge_mcp-0.1.0/docs/ARCHITECTURE.md +139 -0
- cli_bridge_mcp-0.1.0/docs/BENCHMARKS.md +125 -0
- cli_bridge_mcp-0.1.0/docs/COMPARISON.md +71 -0
- cli_bridge_mcp-0.1.0/docs/COSTS.md +123 -0
- cli_bridge_mcp-0.1.0/docs/demo/borrow.tape +40 -0
- cli_bridge_mcp-0.1.0/docs/demo/demo.tape +55 -0
- cli_bridge_mcp-0.1.0/docs/demo/render.sh +33 -0
- cli_bridge_mcp-0.1.0/docs/demo/setup.sh +40 -0
- cli_bridge_mcp-0.1.0/docs/i18n/README.de.md +410 -0
- cli_bridge_mcp-0.1.0/docs/i18n/README.es.md +403 -0
- cli_bridge_mcp-0.1.0/docs/i18n/README.fr.md +414 -0
- cli_bridge_mcp-0.1.0/docs/i18n/README.ja.md +380 -0
- cli_bridge_mcp-0.1.0/docs/i18n/README.pt-BR.md +400 -0
- cli_bridge_mcp-0.1.0/docs/i18n/README.zh-CN.md +367 -0
- cli_bridge_mcp-0.1.0/examples/byo-api-lane.json +29 -0
- cli_bridge_mcp-0.1.0/examples/community-lanes.json +98 -0
- cli_bridge_mcp-0.1.0/examples/free-apis.json +85 -0
- cli_bridge_mcp-0.1.0/examples/github-action-pr-review.yml +44 -0
- cli_bridge_mcp-0.1.0/examples/lanes.example.json +20 -0
- cli_bridge_mcp-0.1.0/examples/llamacpp.lane.json +28 -0
- cli_bridge_mcp-0.1.0/examples/lmstudio.lane.json +26 -0
- cli_bridge_mcp-0.1.0/examples/local-first-host.md +85 -0
- cli_bridge_mcp-0.1.0/examples/mcp.example.json +14 -0
- cli_bridge_mcp-0.1.0/examples/mlx.lane.json +25 -0
- cli_bridge_mcp-0.1.0/pyproject.toml +59 -0
- cli_bridge_mcp-0.1.0/server.json +36 -0
- cli_bridge_mcp-0.1.0/site/index.html +134 -0
- cli_bridge_mcp-0.1.0/smithery.yaml +29 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/__init__.py +2 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/__main__.py +4 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/buildloop.py +379 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/cli.py +438 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/config.py +470 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/conversations.py +111 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/council.py +340 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/detect.py +17 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/eval.py +610 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/findings.py +316 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/guards.py +98 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/jobs.py +167 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/lanes.py +669 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/orchestrate.py +587 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/preamble.py +99 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/router.py +158 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/runner.py +371 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/server.py +2414 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/telemetry.py +745 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/workflows.py +1312 -0
- cli_bridge_mcp-0.1.0/src/cli_bridge/worktrees.py +480 -0
- cli_bridge_mcp-0.1.0/tests/conftest.py +9 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/auth_bypass_none_user/case.diff +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/auth_bypass_none_user/expected.json +25 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/auth_bypass_none_user/ideal.json +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/auth_debug_bypass/case.diff +8 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/auth_debug_bypass/expected.json +25 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/auth_debug_bypass/ideal.json +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/clean_rename/case.diff +12 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/clean_rename/expected.json +12 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/clean_rename/ideal.json +1 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/clean_reorder_imports/case.diff +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/clean_reorder_imports/expected.json +12 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/clean_reorder_imports/ideal.json +1 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/identity_compare_status/case.diff +8 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/identity_compare_status/expected.json +24 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/identity_compare_status/ideal.json +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/index_bounds_ring/case.diff +9 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/index_bounds_ring/expected.json +26 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/index_bounds_ring/ideal.json +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/index_plus_one_loop/case.diff +9 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/index_plus_one_loop/expected.json +24 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/index_plus_one_loop/ideal.json +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/logic_inversion_delete/case.diff +8 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/logic_inversion_delete/expected.json +25 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/logic_inversion_delete/ideal.json +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/logic_negation_flip/case.diff +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/logic_negation_flip/expected.json +25 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/logic_negation_flip/ideal.json +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/missing_return_total/case.diff +9 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/missing_return_total/expected.json +23 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/missing_return_total/ideal.json +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/multibug_bank/case.diff +15 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/multibug_bank/expected.json +35 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/multibug_bank/ideal.json +18 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/multibug_orders/case.diff +15 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/multibug_orders/expected.json +35 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/multibug_orders/ideal.json +18 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/none_config_subscript/case.diff +8 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/none_config_subscript/expected.json +24 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/none_config_subscript/ideal.json +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/null_deref_chained_get/case.diff +8 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/null_deref_chained_get/expected.json +22 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/null_deref_chained_get/ideal.json +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/null_deref_lookup/case.diff +9 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/null_deref_lookup/expected.json +22 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/null_deref_lookup/ideal.json +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/off_by_one_range_loop/case.diff +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/off_by_one_range_loop/expected.json +25 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/off_by_one_range_loop/ideal.json +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/off_by_one_slice/case.diff +11 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/off_by_one_slice/expected.json +25 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/off_by_one_slice/ideal.json +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/race_lazy_singleton/case.diff +11 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/race_lazy_singleton/expected.json +25 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/race_lazy_singleton/ideal.json +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/race_toctou_write/case.diff +11 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/race_toctou_write/expected.json +25 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/race_toctou_write/ideal.json +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/resource_leak_early_return/case.diff +12 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/resource_leak_early_return/expected.json +25 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/resource_leak_early_return/ideal.json +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/socket_leak_error_path/case.diff +15 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/socket_leak_error_path/expected.json +25 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/socket_leak_error_path/ideal.json +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/swallowed_exception_sync/case.diff +11 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/swallowed_exception_sync/expected.json +25 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/evalset/swallowed_exception_sync/ideal.json +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/replies/clean_array.json +6 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/replies/fenced.txt +6 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/replies/garbage.txt +2 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/replies/no_issues.txt +1 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/replies/prose_wrapped.txt +6 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/reviews/clean.diff +11 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/reviews/dangerous_shell.diff +10 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/reviews/multi_issue.diff +22 -0
- cli_bridge_mcp-0.1.0/tests/fixtures/reviews/secret_leak.diff +10 -0
- cli_bridge_mcp-0.1.0/tests/test_build_direct.py +204 -0
- cli_bridge_mcp-0.1.0/tests/test_buildloop.py +242 -0
- cli_bridge_mcp-0.1.0/tests/test_byo_api.py +42 -0
- cli_bridge_mcp-0.1.0/tests/test_cache.py +79 -0
- cli_bridge_mcp-0.1.0/tests/test_challenge.py +54 -0
- cli_bridge_mcp-0.1.0/tests/test_cli.py +172 -0
- cli_bridge_mcp-0.1.0/tests/test_consensus.py +143 -0
- cli_bridge_mcp-0.1.0/tests/test_conversation_dispatch.py +73 -0
- cli_bridge_mcp-0.1.0/tests/test_conversations.py +92 -0
- cli_bridge_mcp-0.1.0/tests/test_cost_defaults.py +112 -0
- cli_bridge_mcp-0.1.0/tests/test_cost_truth.py +169 -0
- cli_bridge_mcp-0.1.0/tests/test_council_recap.py +110 -0
- cli_bridge_mcp-0.1.0/tests/test_debate_hardening.py +371 -0
- cli_bridge_mcp-0.1.0/tests/test_eval_scorer.py +289 -0
- cli_bridge_mcp-0.1.0/tests/test_evals.py +110 -0
- cli_bridge_mcp-0.1.0/tests/test_features.py +282 -0
- cli_bridge_mcp-0.1.0/tests/test_files_required.py +90 -0
- cli_bridge_mcp-0.1.0/tests/test_findings.py +207 -0
- cli_bridge_mcp-0.1.0/tests/test_git_tools.py +73 -0
- cli_bridge_mcp-0.1.0/tests/test_guards.py +132 -0
- cli_bridge_mcp-0.1.0/tests/test_host_sample.py +63 -0
- cli_bridge_mcp-0.1.0/tests/test_integration.py +120 -0
- cli_bridge_mcp-0.1.0/tests/test_isolation.py +51 -0
- cli_bridge_mcp-0.1.0/tests/test_jobs.py +141 -0
- cli_bridge_mcp-0.1.0/tests/test_lanes.py +324 -0
- cli_bridge_mcp-0.1.0/tests/test_live_e2e.py +30 -0
- cli_bridge_mcp-0.1.0/tests/test_local_recipes.py +48 -0
- cli_bridge_mcp-0.1.0/tests/test_model_discovery.py +83 -0
- cli_bridge_mcp-0.1.0/tests/test_nesting_env.py +43 -0
- cli_bridge_mcp-0.1.0/tests/test_opencode_cache.py +57 -0
- cli_bridge_mcp-0.1.0/tests/test_orchestrate.py +464 -0
- cli_bridge_mcp-0.1.0/tests/test_outcome_routing.py +145 -0
- cli_bridge_mcp-0.1.0/tests/test_pacing.py +91 -0
- cli_bridge_mcp-0.1.0/tests/test_preamble.py +70 -0
- cli_bridge_mcp-0.1.0/tests/test_profile.py +58 -0
- cli_bridge_mcp-0.1.0/tests/test_progress.py +47 -0
- cli_bridge_mcp-0.1.0/tests/test_resources.py +45 -0
- cli_bridge_mcp-0.1.0/tests/test_router.py +97 -0
- cli_bridge_mcp-0.1.0/tests/test_runner.py +211 -0
- cli_bridge_mcp-0.1.0/tests/test_server.py +399 -0
- cli_bridge_mcp-0.1.0/tests/test_severity_filter.py +33 -0
- cli_bridge_mcp-0.1.0/tests/test_stance_agreement.py +52 -0
- cli_bridge_mcp-0.1.0/tests/test_telemetry.py +192 -0
- cli_bridge_mcp-0.1.0/tests/test_workflows.py +275 -0
- cli_bridge_mcp-0.1.0/tests/test_worktrees.py +143 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our pledge
|
|
4
|
+
|
|
5
|
+
We — contributors and maintainers — pledge to make participation in this project a harassment-free
|
|
6
|
+
experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and
|
|
7
|
+
expression, level of experience, nationality, personal appearance, race, religion, or sexual
|
|
8
|
+
identity and orientation.
|
|
9
|
+
|
|
10
|
+
## Our standards
|
|
11
|
+
|
|
12
|
+
Examples of behavior that contributes to a positive environment:
|
|
13
|
+
|
|
14
|
+
- Being respectful of differing viewpoints and experiences.
|
|
15
|
+
- Giving and gracefully accepting constructive feedback.
|
|
16
|
+
- Focusing on what is best for the community and the project.
|
|
17
|
+
|
|
18
|
+
Unacceptable behavior includes harassment, insulting or derogatory comments, personal or political
|
|
19
|
+
attacks, publishing others' private information, and other conduct that would reasonably be
|
|
20
|
+
considered inappropriate.
|
|
21
|
+
|
|
22
|
+
## Enforcement
|
|
23
|
+
|
|
24
|
+
Instances of abusive or otherwise unacceptable behavior may be reported to the maintainer via a
|
|
25
|
+
GitHub private security advisory or the contact on the maintainer's profile. All complaints will be
|
|
26
|
+
reviewed and investigated and will result in a response appropriate to the circumstances.
|
|
27
|
+
|
|
28
|
+
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
|
|
29
|
+
version 2.1.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Contributing to cli-bridge
|
|
2
|
+
|
|
3
|
+
Thanks for helping out. cli-bridge aims to stay small, dependency-light, and predictable, so a
|
|
4
|
+
few rules keep it that way.
|
|
5
|
+
|
|
6
|
+
## Setup
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
uv venv && uv pip install -e . pytest pytest-asyncio ruff
|
|
10
|
+
CLI_BRIDGE_STATE_DB=/tmp/t.sqlite pytest -q # keep tests off your real state db
|
|
11
|
+
ruff check src/ tests/
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
No real AI CLI or network is needed to develop or test — lanes are faked with `echo`/`false`
|
|
15
|
+
and the state DB is a temp sqlite.
|
|
16
|
+
|
|
17
|
+
## Ground rules
|
|
18
|
+
|
|
19
|
+
- **Stdlib + `mcp` only.** No new runtime dependencies. (Dev tools like `pytest`/`ruff` are fine.)
|
|
20
|
+
- **Keep `server.py` thin.** Business logic lives in `lanes`/`runner`/`router`/`workflows`/
|
|
21
|
+
`findings`/`telemetry`. The server routes; it doesn't decide.
|
|
22
|
+
- **Every change ships a test**, and `pytest -q` + `ruff check` must stay green. Tests must not
|
|
23
|
+
require a real CLI or network.
|
|
24
|
+
- **Portable**: macOS / Linux / Windows. No POSIX-only calls without a Windows branch
|
|
25
|
+
(see `runner._kill_tree`).
|
|
26
|
+
- **Telemetry is best-effort** — it must NEVER raise into a delegation path.
|
|
27
|
+
- **Cost safety**: a missing/empty model must never resolve to a paid model.
|
|
28
|
+
- **Surgical diffs.** Match the existing style; don't reformat unrelated code.
|
|
29
|
+
|
|
30
|
+
## Adding a lane (a new CLI)
|
|
31
|
+
|
|
32
|
+
You usually don't need to fork: point `CLI_BRIDGE_LANES_FILE` at a JSON file
|
|
33
|
+
(see `examples/lanes.example.json`), or wrap an HTTP API with `curl`
|
|
34
|
+
(`examples/byo-api-lane.json`). To add a built-in lane, append a `LaneSpec` in `lanes.py` with
|
|
35
|
+
its argv builder and capabilities, and ship a test. See `docs/ARCHITECTURE.md` → "Extending it".
|
|
36
|
+
|
|
37
|
+
## Adding a workflow
|
|
38
|
+
|
|
39
|
+
Add a function in `workflows.py` taking `(targets, args, run_lane)`, then register a tool +
|
|
40
|
+
dispatch in `server.py` (and a prompt in `_PROMPTS` if it deserves a slash command). Reuse the
|
|
41
|
+
injected `run_lane` so it's testable with fakes.
|
|
42
|
+
|
|
43
|
+
## Commit / PR
|
|
44
|
+
|
|
45
|
+
- Conventional-commit-ish subjects (`feat(...)`, `fix(...)`, `docs(...)`) are appreciated.
|
|
46
|
+
- Describe the *why*, not just the *what*. Note any new env var or tool.
|
|
47
|
+
- Update `CHANGELOG.md` under "Unreleased".
|
|
48
|
+
|
|
49
|
+
By contributing you agree your work is licensed under the project's MIT license.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Something in cli-bridge behaves incorrectly
|
|
4
|
+
title: "[bug] "
|
|
5
|
+
labels: bug
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
**What happened**
|
|
9
|
+
A clear description of the bug.
|
|
10
|
+
|
|
11
|
+
**Steps to reproduce**
|
|
12
|
+
1. Host (Claude Code / Codex / opencode / …) and version:
|
|
13
|
+
2. Tool called and arguments:
|
|
14
|
+
3. What you expected vs. what happened:
|
|
15
|
+
|
|
16
|
+
**`doctor` output**
|
|
17
|
+
Paste the output of the `doctor` tool (or `cli-bridge doctor`).
|
|
18
|
+
|
|
19
|
+
**Environment**
|
|
20
|
+
- cli-bridge version / commit:
|
|
21
|
+
- OS:
|
|
22
|
+
- Python:
|
|
23
|
+
- Relevant `CLI_BRIDGE_*` env vars (redact secrets):
|
|
24
|
+
|
|
25
|
+
**Logs**
|
|
26
|
+
If you can, set `CLI_BRIDGE_LOG=<path>` and attach the relevant lines (redact secrets).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: CLI flag broken / drifted
|
|
3
|
+
about: A delegate CLI changed its flags and a lane no longer works
|
|
4
|
+
title: "[drift] "
|
|
5
|
+
labels: drift
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
**Which lane / CLI?**
|
|
9
|
+
e.g. `gemini` (agy), `gpt` (codex), `opencode` …
|
|
10
|
+
|
|
11
|
+
**What broke**
|
|
12
|
+
The error or wrong behavior you see (paste the `[kind] message`, e.g. `[failed] … exit 2`).
|
|
13
|
+
|
|
14
|
+
**The CLI's current invocation**
|
|
15
|
+
What the CLI now expects (paste the relevant `--help` excerpt):
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
<paste mycli --help here>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Version**
|
|
22
|
+
- CLI version:
|
|
23
|
+
- cli-bridge version / commit:
|
|
24
|
+
|
|
25
|
+
> The nightly `drift-check` workflow opens these automatically when the test suite breaks; feel
|
|
26
|
+
> free to file one manually if you hit it first.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Lane request
|
|
3
|
+
about: Ask for a new built-in CLI lane
|
|
4
|
+
title: "[lane] "
|
|
5
|
+
labels: lane-request
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
**Which CLI?**
|
|
9
|
+
Name + link to the official CLI.
|
|
10
|
+
|
|
11
|
+
**Install + auth**
|
|
12
|
+
How is it installed, and how does a user log in (subscription / free tier / API key)?
|
|
13
|
+
|
|
14
|
+
**Invocation**
|
|
15
|
+
The exact non-interactive command to send one prompt and get an answer, e.g.:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
mycli --print "the prompt"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- Model selection flag (if any):
|
|
22
|
+
- Reasoning/effort flag (if any):
|
|
23
|
+
- Write/build mode flag (if any):
|
|
24
|
+
- How to list models (if any):
|
|
25
|
+
|
|
26
|
+
**Cost**
|
|
27
|
+
Is it free, quota-limited, or paid/credits for a typical user?
|
|
28
|
+
|
|
29
|
+
> Tip: you may not need a built-in lane — you can add any CLI yourself via
|
|
30
|
+
> `CLI_BRIDGE_LANES_FILE` (see `examples/lanes.example.json`).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!-- Thanks for contributing! Keep diffs surgical and tests green. -->
|
|
2
|
+
|
|
3
|
+
## What & why
|
|
4
|
+
<!-- What does this change, and why? Link any issue. -->
|
|
5
|
+
|
|
6
|
+
## Checklist
|
|
7
|
+
- [ ] `pytest -q` is green (tests don't need a real CLI or network)
|
|
8
|
+
- [ ] `ruff check src/ tests/` is clean
|
|
9
|
+
- [ ] No new runtime dependency (stdlib + `mcp` only)
|
|
10
|
+
- [ ] `server.py` stays thin (logic lives in lanes/runner/router/workflows/findings/telemetry)
|
|
11
|
+
- [ ] Telemetry stays best-effort (never raises into a delegation)
|
|
12
|
+
- [ ] Cost safety preserved (empty model never resolves to paid)
|
|
13
|
+
- [ ] `CHANGELOG.md` updated under "Unreleased" (if user-facing)
|
|
14
|
+
- [ ] New env var / tool documented (README / ARCHITECTURE)
|
|
15
|
+
|
|
16
|
+
## Notes
|
|
17
|
+
<!-- Anything reviewers should know: new env vars, behavior changes, follow-ups. -->
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
cli-bridge spawns official AI CLIs as subprocesses and returns their output to your host
|
|
4
|
+
assistant. That puts it between two trust boundaries, so it's worth being precise about what it
|
|
5
|
+
defends against and what it does not.
|
|
6
|
+
|
|
7
|
+
## Reporting a vulnerability
|
|
8
|
+
|
|
9
|
+
Please **do not** open a public issue for a security problem. Instead, open a
|
|
10
|
+
[GitHub security advisory](https://github.com/JoaoBerne/cli-bridge-mcp/security/advisories/new)
|
|
11
|
+
(or email the maintainer listed on the GitHub profile). Include repro steps and the affected
|
|
12
|
+
version/commit. You'll get an acknowledgement within a few days.
|
|
13
|
+
|
|
14
|
+
## Threat model
|
|
15
|
+
|
|
16
|
+
cli-bridge handles two kinds of untrusted data:
|
|
17
|
+
|
|
18
|
+
1. **Delegate output** — text produced by another model/CLI, returned to your host assistant.
|
|
19
|
+
It can contain prompt-injection ("ignore previous instructions"), requests to exfiltrate
|
|
20
|
+
secrets, hidden instructions in HTML comments, or shell commands disguised as guidance.
|
|
21
|
+
2. **The task/diff you pass in** — may itself contain secrets or hostile content.
|
|
22
|
+
|
|
23
|
+
### What cli-bridge does about it
|
|
24
|
+
|
|
25
|
+
- **Ban-safe by construction.** It only ever spawns the official CLI you already run. It never
|
|
26
|
+
extracts tokens, reads credential files, or sends your API keys anywhere. (Test: `test_isolation.py`.)
|
|
27
|
+
- **No pollution of your CLI config.** The only things it writes are an overflow temp file, the
|
|
28
|
+
local telemetry sqlite, and an optional log. Never to `~/.gemini`, `~/.codex`, etc.
|
|
29
|
+
- **Secret redaction.** Known secret shapes (bearer tokens, `sk-…`, `ghp_…`, `AIza…`,
|
|
30
|
+
`api_key=…`) are redacted from output **before** anything is returned or logged
|
|
31
|
+
(`runner.redact`). The review prechecks redact a secret's value even while flagging it.
|
|
32
|
+
- **Output guard.** `CLI_BRIDGE_GUARD=off|warn|strict` (default `warn`) scans delegate output for
|
|
33
|
+
injection / tool-poisoning signals and, in `warn`, prepends a banner telling the host to treat
|
|
34
|
+
the text as **data, not instructions**; in `strict`, it withholds the body. Runs after redaction.
|
|
35
|
+
- **Read-only by default.** A delegate can only edit files with an explicit `agent: build`. The
|
|
36
|
+
recommended way to use write mode is **`ask_build_isolated`**, which runs the agent in a
|
|
37
|
+
throwaway git worktree and returns a diff — your real repository is never modified.
|
|
38
|
+
- **Cost safety.** A missing/empty model never resolves to a paid model; `ask_all`/`ask_cascade`
|
|
39
|
+
exclude limited/paid lanes by default.
|
|
40
|
+
- **Telemetry is local and minimal.** A task **hash + short preview** only (never the full
|
|
41
|
+
prompt/output unless you set `CLI_BRIDGE_STORE_TRANSCRIPTS=true`), in a local sqlite DB that
|
|
42
|
+
never leaves your machine. Disable with `CLI_BRIDGE_TELEMETRY=off`.
|
|
43
|
+
|
|
44
|
+
### What it does NOT protect against
|
|
45
|
+
|
|
46
|
+
- **It is not a sandbox.** A delegate CLI runs with your user's permissions. In `agent: build`
|
|
47
|
+
(outside `ask_build_isolated`) it can modify files; only run write mode on code you trust.
|
|
48
|
+
- **The guard is heuristic.** It catches high-signal patterns, not every possible injection. In
|
|
49
|
+
`warn` mode the text still reaches the host — treat delegated output as untrusted input.
|
|
50
|
+
- **It can't vet the models themselves.** A compromised or malicious model could emit harmful
|
|
51
|
+
content; that's why output is annotated and, for writes, isolated.
|
|
52
|
+
- **`cwd`/path arguments are not jailed.** A delegate sees whatever directory you point it at.
|
|
53
|
+
- **Delegates inherit your full environment.** Each spawned CLI gets the host process's complete
|
|
54
|
+
`os.environ` — deliberately, because official CLIs need their own `PATH`, auth caches and
|
|
55
|
+
config to work. The flip side: every secret in that environment (cloud creds, unrelated API
|
|
56
|
+
keys) is readable by any delegate process, exactly as if you ran that CLI by hand. cli-bridge
|
|
57
|
+
redacts known secret shapes from *output*, but it cannot stop a malicious CLI from reading the
|
|
58
|
+
env it was born with. If that matters, launch your MCP host from a scoped environment (e.g.
|
|
59
|
+
`env -i PATH=… HOME=…` or a shell profile without the sensitive exports).
|
|
60
|
+
- **BYO-API (curl) lanes: keep the key OUT of argv.** A lane that substitutes a `${ENV}` key into
|
|
61
|
+
a `curl` command line puts that key in this machine's process list for the duration of the call
|
|
62
|
+
(it is never logged — traces redact it, and it never leaves your machine otherwise). The shipped
|
|
63
|
+
examples use the secret-safe pattern instead — `--variable %MY_KEY` + `--expand-header
|
|
64
|
+
"Authorization: Bearer {{MY_KEY}}"` (curl ≥ 8.3) imports the key *inside* curl, so `ps` only
|
|
65
|
+
ever shows the variable's name. `doctor` warns about any custom lane that still expands a
|
|
66
|
+
secret into a credential-bearing argv part.
|
|
67
|
+
|
|
68
|
+
## Hardening checklist for sensitive use
|
|
69
|
+
|
|
70
|
+
- Set `CLI_BRIDGE_GUARD=strict`.
|
|
71
|
+
- Use `ask_build_isolated` instead of raw `agent: build`.
|
|
72
|
+
- Keep `CLI_BRIDGE_STORE_TRANSCRIPTS` unset.
|
|
73
|
+
- Run from a directory that contains only what the delegate should see.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Nightly "does it still import / do the lane builders still hold" check.
|
|
2
|
+
# CLIs change their flags without warning; this catches obvious breakage early and opens
|
|
3
|
+
# an issue so the lane builders can be updated before users hit it.
|
|
4
|
+
name: drift-check
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
schedule:
|
|
8
|
+
- cron: "0 6 * * *" # daily 06:00 UTC
|
|
9
|
+
workflow_dispatch: {}
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
issues: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
smoke:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v5
|
|
20
|
+
- uses: actions/setup-python@v6
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.12"
|
|
23
|
+
- run: |
|
|
24
|
+
python -m pip install --upgrade pip
|
|
25
|
+
pip install -e . pytest pytest-asyncio
|
|
26
|
+
- name: Import + unit tests
|
|
27
|
+
id: check
|
|
28
|
+
run: pytest -q
|
|
29
|
+
- name: Open an issue on failure
|
|
30
|
+
if: failure()
|
|
31
|
+
uses: actions/github-script@v7
|
|
32
|
+
with:
|
|
33
|
+
script: |
|
|
34
|
+
const title = "drift-check failed — a CLI lane may be broken";
|
|
35
|
+
const body = "The nightly drift-check run failed. A delegate CLI may have changed its " +
|
|
36
|
+
"flags, or a dependency broke. See the failing run: " +
|
|
37
|
+
`${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
|
38
|
+
const existing = await github.rest.issues.listForRepo({
|
|
39
|
+
owner: context.repo.owner, repo: context.repo.repo,
|
|
40
|
+
state: "open", labels: "drift",
|
|
41
|
+
});
|
|
42
|
+
if (existing.data.length === 0) {
|
|
43
|
+
await github.rest.issues.create({
|
|
44
|
+
owner: context.repo.owner, repo: context.repo.repo,
|
|
45
|
+
title, body, labels: ["drift"],
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# GitHub Pages — landing page (site/index.html + assets/).
|
|
2
|
+
# Manual trigger only: Pages on the free plan needs the repo public, so this is armed for
|
|
3
|
+
# launch day (run it once from the Actions tab after going public + enabling Pages → Actions).
|
|
4
|
+
name: pages
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
pages: write
|
|
12
|
+
id-token: write
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: pages
|
|
16
|
+
cancel-in-progress: true
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
deploy:
|
|
20
|
+
environment:
|
|
21
|
+
name: github-pages
|
|
22
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v5
|
|
26
|
+
- name: Assemble site
|
|
27
|
+
run: |
|
|
28
|
+
mkdir -p _site
|
|
29
|
+
cp site/index.html _site/
|
|
30
|
+
cp -r assets _site/assets
|
|
31
|
+
- uses: actions/upload-pages-artifact@v3
|
|
32
|
+
with:
|
|
33
|
+
path: _site
|
|
34
|
+
- id: deployment
|
|
35
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Publish to PyPI on a version tag, via Trusted Publishing (OIDC — no API token stored).
|
|
2
|
+
# One-time setup on PyPI: add a "pending publisher" for project `cli-bridge-mcp`, repo
|
|
3
|
+
# JoaoBerne/cli-bridge-mcp, workflow `release.yml`, environment `pypi`. Then `git tag v0.1.0 &&
|
|
4
|
+
# git push --tags` builds + publishes automatically.
|
|
5
|
+
name: release
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags: ["v*"]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v5
|
|
19
|
+
- uses: actions/setup-python@v6
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.12"
|
|
22
|
+
- name: Build sdist + wheel
|
|
23
|
+
run: |
|
|
24
|
+
python -m pip install --upgrade pip build
|
|
25
|
+
python -m build
|
|
26
|
+
- name: Smoke-check the wheel imports + entry points
|
|
27
|
+
run: |
|
|
28
|
+
python -m pip install dist/*.whl
|
|
29
|
+
python -c "import cli_bridge, cli_bridge.cli, cli_bridge.server; print('import ok')"
|
|
30
|
+
cli-bridge --help >/dev/null && echo "cli-bridge entry ok"
|
|
31
|
+
- uses: actions/upload-artifact@v4
|
|
32
|
+
with:
|
|
33
|
+
name: dist
|
|
34
|
+
path: dist/
|
|
35
|
+
|
|
36
|
+
publish:
|
|
37
|
+
needs: build
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
environment: pypi
|
|
40
|
+
permissions:
|
|
41
|
+
id-token: write # required for Trusted Publishing
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/download-artifact@v4
|
|
44
|
+
with:
|
|
45
|
+
name: dist
|
|
46
|
+
path: dist/
|
|
47
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
# PR-only: a rebase-merge lands the exact commits the PR already tested, so a separate
|
|
5
|
+
# push-on-main run would re-test identical commits — pure duplication. Dropped to halve CI cost.
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, master]
|
|
8
|
+
workflow_dispatch: # manual full-matrix run on main when you want one (e.g. before a release)
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
lint:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v5
|
|
15
|
+
- uses: actions/setup-python@v6
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.12"
|
|
18
|
+
- name: Install ruff
|
|
19
|
+
run: python -m pip install --upgrade pip ruff
|
|
20
|
+
- name: Lint
|
|
21
|
+
run: ruff check src/ tests/
|
|
22
|
+
|
|
23
|
+
typecheck:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v5
|
|
27
|
+
- uses: actions/setup-python@v6
|
|
28
|
+
with:
|
|
29
|
+
python-version: "3.12"
|
|
30
|
+
- name: Install
|
|
31
|
+
# mypy pinned so the gate can't pass on one version and break on another; install the
|
|
32
|
+
# package so mypy resolves mcp's own types.
|
|
33
|
+
run: |
|
|
34
|
+
python -m pip install --upgrade pip
|
|
35
|
+
pip install -e . mypy==2.1.0
|
|
36
|
+
- name: Type check
|
|
37
|
+
run: mypy src/cli_bridge
|
|
38
|
+
|
|
39
|
+
test:
|
|
40
|
+
runs-on: ${{ matrix.os }}
|
|
41
|
+
strategy:
|
|
42
|
+
fail-fast: false
|
|
43
|
+
matrix:
|
|
44
|
+
os: [ubuntu-latest]
|
|
45
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
46
|
+
include:
|
|
47
|
+
# prove portability (a stated invariant). macOS minutes bill at 10× on GitHub-hosted
|
|
48
|
+
# runners, so we keep a single macOS job (newest Python) and cover both ends of the
|
|
49
|
+
# version range on Windows (2×) instead. Full cross-product on demand via workflow_dispatch.
|
|
50
|
+
- os: macos-latest
|
|
51
|
+
python-version: "3.13"
|
|
52
|
+
- os: windows-latest
|
|
53
|
+
python-version: "3.10"
|
|
54
|
+
- os: windows-latest
|
|
55
|
+
python-version: "3.13"
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/checkout@v5
|
|
58
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
59
|
+
uses: actions/setup-python@v6
|
|
60
|
+
with:
|
|
61
|
+
python-version: ${{ matrix.python-version }}
|
|
62
|
+
- name: Install
|
|
63
|
+
run: |
|
|
64
|
+
python -m pip install --upgrade pip
|
|
65
|
+
pip install -e . pytest pytest-asyncio
|
|
66
|
+
- name: Run tests
|
|
67
|
+
run: pytest -q
|
|
68
|
+
|
|
69
|
+
security:
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
permissions:
|
|
72
|
+
contents: read
|
|
73
|
+
pull-requests: read # gitleaks-action lists the PR's commits via the API in PR mode
|
|
74
|
+
steps:
|
|
75
|
+
- uses: actions/checkout@v5
|
|
76
|
+
with:
|
|
77
|
+
fetch-depth: 0 # gitleaks scans full history; dep-review diffs the PR base/head
|
|
78
|
+
# Fail a PR that ADDS a dependency carrying a high+ CVE. PR-scoped, native to GitHub, free,
|
|
79
|
+
# zero runtime cost — right-sized for a stdlib + `mcp` project (no Trivy: it would find ~0
|
|
80
|
+
# on a one-dependency tree and just burn CI minutes). Hard gate now that the repo is public
|
|
81
|
+
# (the dependency-graph API is on by default for public repos): a PR adding a high+ CVE dep fails.
|
|
82
|
+
- name: Dependency review
|
|
83
|
+
if: github.event_name == 'pull_request'
|
|
84
|
+
uses: actions/dependency-review-action@v4
|
|
85
|
+
with:
|
|
86
|
+
fail-on-severity: high
|
|
87
|
+
# Secret scan — the real risk here: the whole pitch is "no token leak, no key extraction".
|
|
88
|
+
# gitleaks-action is free for personal-account repos (no GITLEAKS_LICENSE needed).
|
|
89
|
+
- name: Secret scan (gitleaks)
|
|
90
|
+
uses: gitleaks/gitleaks-action@v2
|
|
91
|
+
env:
|
|
92
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.venv/
|
|
9
|
+
venv/
|
|
10
|
+
|
|
11
|
+
# Secrets / local state (never commit)
|
|
12
|
+
.env
|
|
13
|
+
.env.*
|
|
14
|
+
*.sqlite
|
|
15
|
+
*.sqlite-wal
|
|
16
|
+
*.sqlite-shm
|
|
17
|
+
|
|
18
|
+
# Test / tooling
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.coverage
|
|
21
|
+
htmlcov/
|
|
22
|
+
.ruff_cache/
|
|
23
|
+
.mypy_cache/
|
|
24
|
+
|
|
25
|
+
# OS / editor
|
|
26
|
+
.DS_Store
|
|
27
|
+
*.swp
|
|
28
|
+
.idea/
|
|
29
|
+
.vscode/
|
|
30
|
+
TESTING.md
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# gitleaks configuration — extends the default ruleset.
|
|
2
|
+
#
|
|
3
|
+
# The test tree carries INTENTIONAL synthetic secrets (fake `ghp_…`, `sk-…`, Slack-webhook
|
|
4
|
+
# strings) used as fixtures to verify cli-bridge's OWN secret redaction + precheck detection.
|
|
5
|
+
# They are not real credentials — policy is that tests never use real keys (see AGENTS.md/CLAUDE.md).
|
|
6
|
+
# Allowlist the test tree so gitleaks doesn't flag its own bait; `src/` stays fully scanned.
|
|
7
|
+
[extend]
|
|
8
|
+
useDefault = true
|
|
9
|
+
|
|
10
|
+
[allowlist]
|
|
11
|
+
description = "Synthetic secrets in the test suite (redaction/precheck fixtures, not real credentials)"
|
|
12
|
+
paths = [
|
|
13
|
+
'''^tests/''',
|
|
14
|
+
]
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Agent guide — cli-bridge
|
|
2
|
+
|
|
3
|
+
Guidance for AI coding agents (Claude Code, Codex, Gemini CLI, opencode…) working on this
|
|
4
|
+
repo. `CLAUDE.md` is a symlink to this file.
|
|
5
|
+
|
|
6
|
+
## What this is
|
|
7
|
+
|
|
8
|
+
An MCP server that lets the host AI consult **other** AI CLIs as a council. Each lane spawns
|
|
9
|
+
the official CLI as a subprocess (ban-safe: no token extraction, no API keys). Read-only by
|
|
10
|
+
default. Pure-stdlib + `mcp` only.
|
|
11
|
+
|
|
12
|
+
## Layout
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
src/cli_bridge/
|
|
16
|
+
server.py # MCP surface: tool list, dispatch, doctor/setup. Keep thin.
|
|
17
|
+
lanes.py # LaneSpec registry + argv builders + custom-lane JSON loader
|
|
18
|
+
runner.py # subprocess exec, redaction, process-tree kill, error classification
|
|
19
|
+
config.py # env parsing, cost profile, timeouts, onboarding text
|
|
20
|
+
telemetry.py # sqlite3 run log + lane health/cooldown (best-effort, privacy-first)
|
|
21
|
+
router.py # deterministic cascade ordering (pure)
|
|
22
|
+
council.py # ask_all/ask_cascade/ask_best/synthesize fan-out (injected run_lane — like workflows)
|
|
23
|
+
jobs.py # in-process async jobs (ask_all_async) + sqlite persistence
|
|
24
|
+
workflows.py # review_diff/security_review/debate + prechecks + council recap
|
|
25
|
+
orchestrate.py # batch_run durable fan-out + presets (refine_plan/verify_repair/fanout_compare/…)
|
|
26
|
+
findings.py # parse/merge/render structured review findings (pure)
|
|
27
|
+
guards.py # injection/tool-poisoning output guard (CLI_BRIDGE_GUARD)
|
|
28
|
+
worktrees.py # ask_build (isolated worktree diff | direct zone-guarded write + artifact return)
|
|
29
|
+
buildloop.py # steerable multi-turn builds: job_tail/build_steer, executable DoD gate
|
|
30
|
+
conversations.py # round-table threads: sqlite persistence + recipient-aware replay
|
|
31
|
+
preamble.py # terse response-style preamble prepended to delegate prompts
|
|
32
|
+
eval.py # quality eval: council vs single + self-consistency, permutation test, scorer
|
|
33
|
+
cli.py # human/CI entry point (cli-bridge ...) over the same internals
|
|
34
|
+
detect.py # PATH detection
|
|
35
|
+
tests/ # pytest; unit + cross-host integration (no real CLI needed)
|
|
36
|
+
docs/ # COSTS.md, BENCHMARKS.md, ARCHITECTURE.md, i18n/ READMEs
|
|
37
|
+
assets/ # README banner/mark/social SVGs + demo.gif (generated, do not hand-edit)
|
|
38
|
+
site/ # GitHub Pages landing (deployed by .github/workflows/pages.yml)
|
|
39
|
+
examples/ # custom-lane JSON recipes + GitHub Action
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Rules for changes
|
|
43
|
+
|
|
44
|
+
- **Keep `server.py` thin** — business logic belongs in lanes/runner/router/telemetry.
|
|
45
|
+
- **No new runtime deps** beyond `mcp`. Stdlib only.
|
|
46
|
+
- **Every change ships tests.** `pytest -q` must stay green. Tests must not need a real AI
|
|
47
|
+
CLI or network (fake lanes via `echo`/`false`, temp sqlite via `CLI_BRIDGE_STATE_DB`).
|
|
48
|
+
- **Portability**: must run on macOS/Linux/Windows. No POSIX-only calls without a Windows
|
|
49
|
+
branch (see `runner._kill_tree`).
|
|
50
|
+
- **Telemetry is best-effort** — it must NEVER raise into a delegation path.
|
|
51
|
+
- **Cost safety**: a missing/empty model must never resolve to a paid model. `ask_all`/
|
|
52
|
+
`ask_cascade` exclude limited/paid by default.
|
|
53
|
+
- Match existing style; surgical diffs.
|
|
54
|
+
|
|
55
|
+
## Commands
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
uv venv && uv pip install -e . pytest pytest-asyncio ruff
|
|
59
|
+
pytest -q
|
|
60
|
+
CLI_BRIDGE_STATE_DB=/tmp/t.sqlite pytest -q # keep tests off your real state db
|
|
61
|
+
ruff check src/ tests/ # lint (CI enforces this)
|
|
62
|
+
CLI_BRIDGE_LIVE_E2E=1 pytest tests/test_live_e2e.py -q # opt-in live checks
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Roadmap
|
|
66
|
+
|
|
67
|
+
See `CHANGELOG.md` for the shipped history. Done: config extraction, telemetry+cooldown, cascade router,
|
|
68
|
+
terse preamble (+min-chars skip), response cache, trace fields, workflow tools (review_diff/
|
|
69
|
+
security_review/debate), council recap, MCP prompts, opt-in write/build mode (all lanes),
|
|
70
|
+
sibling-model self-consultation, in-process async jobs (ask_all_async), structured findings
|
|
71
|
+
JSON + deterministic merge + prechecks + residual_risk, output guard (injection/poisoning),
|
|
72
|
+
worktree-isolated write mode (ask_build_isolated), ask_best mode router + estimated token/
|
|
73
|
+
credit accounting (usage_report/usage_budget), human CLI (cli-bridge), MCP resources,
|
|
74
|
+
premortem/test_plan workflows, eval fixtures + no-network evaluator, ruff lint + CI lint job,
|
|
75
|
+
modular tool loading (DISABLED_TOOLS/ENABLED_TOOLS), quality eval (`cli-bridge eval`: council vs
|
|
76
|
+
single-model + self-consistency, deterministic scorer + calibration gate), architect/editor build
|
|
77
|
+
split, debate VOTE footer + convergence early-stop, files_required grounding gate, anti-burst
|
|
78
|
+
spawn pacing (per-lane MIN_INTERVAL_S), trace-footer toggle, i18n READMEs (docs/i18n/), Pages
|
|
79
|
+
landing (site/), opt-in streaming runner (arun on_line/log_path + stall guard), direct builds
|
|
80
|
+
(ask_build mode=isolated|direct — zone contract + per-zone lock + post-turn zone-violation check +
|
|
81
|
+
greenfield init), steerable multi-turn builds (buildloop: job_tail/build_steer/interrupt, executable
|
|
82
|
+
DoD gate, plan-leak warning), durable journaled fan-out (batch_run + resume_id across restart) with
|
|
83
|
+
workflow presets (council_review/map_review/research_verify/refine_plan), guard NFKC/zero-width
|
|
84
|
+
normalization, runtime paid-model warning, **council module extraction** (`council.py`, injected
|
|
85
|
+
run_lane), **mypy gate in CI** (typed `_ann()` helper for the SDK-stub noise), **eval v3** (seeded
|
|
86
|
+
permutation test replacing the 1-sigma heuristic + multi-bug fixtures + in-fixture decoys),
|
|
87
|
+
**build artifact-return** (non-text files surfaced by path — capability-borrowing), **cross-model
|
|
88
|
+
`verify_repair`** + **`fanout_compare`** workflow presets. **Dynamic orchestration engine (Phase 1):**
|
|
89
|
+
typed result envelope + provenance, `findings.extract_json` contract, per-invocation budget caps +
|
|
90
|
+
`dry_run` cost envelope, **cross-vendor `jury`** (author≠reviewer family, k-of-N fail-closed) +
|
|
91
|
+
`lanes.family_of`, **disagreement-as-uncertainty** score on `ask_all`, opt-in **confidence-escalate**
|
|
92
|
+
cascade, **`BRIDGE_DEPTH` re-entry guard**, **`CLI_BRIDGE_LEAN`** core surface, `role=` personas,
|
|
93
|
+
Gemini `images=` vision (experimental). **Local + council quality + quota resilience:** **Ollama lane**
|
|
94
|
+
(`ask_ollama`/`list_ollama_models` — local, $0, offline, read-only) + local-model custom-lane recipes
|
|
95
|
+
(`examples/`), **peer-anonymized debate/council** (neutral Reviewer/Debater labels so no model favours
|
|
96
|
+
a known rival), **`seat_report`** earn-their-seat (`jury_outcomes` telemetry benches dead-weight lanes
|
|
97
|
+
on evidence), **discrete calibration binning** (eval bins on emitted confidences, N≥50 gate),
|
|
98
|
+
**quota-empty cooldown with capped exponential backoff** (never infinite, success-resets).
|
|
99
|
+
|
|
100
|
+
### Considered & deferred (rationale — not just "not yet")
|
|
101
|
+
- **forced-pacing engine** — contradicts the model (cli-bridge delegates investigation to the
|
|
102
|
+
council; it doesn't force the host to slow down), big effort, low value, and only helps if the
|
|
103
|
+
host respects the gate. Dropped.
|
|
104
|
+
- **warm pool** — no lane offers a daemon/server mode (the lanes are spawn-per-call), so there's
|
|
105
|
+
nothing to keep warm. Infeasible cleanly.
|
|
106
|
+
- **chars/token calibration per lane** — would need real provider token counts = ban-risk fishing,
|
|
107
|
+
against the no-extraction ethos. The chars/4 figure stays honestly labeled "estimated".
|
|
108
|
+
- **native build resume (`--session-id`)** — no lane exposes a reliable session id; filesystem
|
|
109
|
+
continuity (the delegate re-reads its own files each turn) already covers it. Revisit if a CLI
|
|
110
|
+
ships a stable resume handle.
|
|
111
|
+
- **Big orchestration architecture** (recursive spawn trees, shared state bus, inter-agent mailbox,
|
|
112
|
+
capability passthrough hub, a wire-protocol "bus between AIs") — real directions, but vaporware-
|
|
113
|
+
prone and vendor-hostile (we build on CLIs we don't control). Roadmap only; positioned honestly
|
|
114
|
+
as a direction, never sold as a shipped protocol. See `docs/ARCHITECTURE.md` for the framing.
|
|
115
|
+
|
|
116
|
+
### Next candidates (small — after real-usage soak of the engine; council-trimmed)
|
|
117
|
+
- **early-stop `ask_all`** (`agree_stop`) — stop spawning once K lanes agree (reuses the agreement
|
|
118
|
+
score). Deferred: needs ask_all restructured from gather-all to incremental + cancellation, and
|
|
119
|
+
the agreement heuristic validated on real outputs first.
|
|
120
|
+
- **architect/editor split** for `ask_build` (plan on a strong lane, edit on a cheap diff-precise one).
|
|
121
|
+
- GATED (build on real demand, not speculatively): **planner → plan_build**, **precommit** (≈
|
|
122
|
+
`review_diff role=strict`), **MoA** preset. CUT: **debug**, **conversation_resume**, generic
|
|
123
|
+
chaining **DSL** / **recursive spawn** (the host already orchestrates; see deferred above).
|
|
124
|
+
- Release track: opt-in real-CLI contract job in CI, history secrets scan, PyPI publish (GO-gated).
|