context-guard-cli 2.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.
- context_guard_cli-2.1.0/.agents/rules/context-guard.md +15 -0
- context_guard_cli-2.1.0/.githooks/pre-commit +194 -0
- context_guard_cli-2.1.0/.github/workflows/ci.yml +27 -0
- context_guard_cli-2.1.0/.github/workflows/publish.yml +60 -0
- context_guard_cli-2.1.0/.gitignore +10 -0
- context_guard_cli-2.1.0/AGENTS.md +93 -0
- context_guard_cli-2.1.0/CHANGELOG.md +271 -0
- context_guard_cli-2.1.0/LICENSE +21 -0
- context_guard_cli-2.1.0/PKG-INFO +296 -0
- context_guard_cli-2.1.0/README.es.md +286 -0
- context_guard_cli-2.1.0/README.md +283 -0
- context_guard_cli-2.1.0/TUTORIAL.es.md +178 -0
- context_guard_cli-2.1.0/context_guard/__init__.py +1 -0
- context_guard_cli-2.1.0/context_guard/_data/hosts/antigravity/hooks.snippet.json +16 -0
- context_guard_cli-2.1.0/context_guard/_data/hosts/antigravity/rules/context-guard.md +15 -0
- context_guard_cli-2.1.0/context_guard/_data/hosts/claude-code/commands/cg-continue.md +13 -0
- context_guard_cli-2.1.0/context_guard/_data/hosts/claude-code/commands/cg-new.md +11 -0
- context_guard_cli-2.1.0/context_guard/_data/hosts/claude-code/mcp.snippet.json +7 -0
- context_guard_cli-2.1.0/context_guard/_data/hosts/claude-code/settings.snippet.json +12 -0
- context_guard_cli-2.1.0/context_guard/_data/hosts/opencode/agent.snippet.json +7 -0
- context_guard_cli-2.1.0/context_guard/_data/hosts/opencode/commands/cg-continue.md +16 -0
- context_guard_cli-2.1.0/context_guard/_data/hosts/opencode/commands/cg-new.md +12 -0
- context_guard_cli-2.1.0/context_guard/_data/hosts/opencode/mcp.snippet.json +9 -0
- context_guard_cli-2.1.0/context_guard/_data/hosts/opencode/permissions.snippet.json +12 -0
- context_guard_cli-2.1.0/context_guard/_data/phases/execute.md +99 -0
- context_guard_cli-2.1.0/context_guard/_data/phases/plan.md +136 -0
- context_guard_cli-2.1.0/context_guard/_data/phases/verify.md +129 -0
- context_guard_cli-2.1.0/context_guard/guard/__init__.py +1 -0
- context_guard_cli-2.1.0/context_guard/guard/assets.py +94 -0
- context_guard_cli-2.1.0/context_guard/guard/cli.py +307 -0
- context_guard_cli-2.1.0/context_guard/guard/commands.py +811 -0
- context_guard_cli-2.1.0/context_guard/guard/errors.py +71 -0
- context_guard_cli-2.1.0/context_guard/guard/locking.py +181 -0
- context_guard_cli-2.1.0/context_guard/guard/manifest.py +69 -0
- context_guard_cli-2.1.0/context_guard/guard/migrate.py +288 -0
- context_guard_cli-2.1.0/context_guard/guard/paths.py +199 -0
- context_guard_cli-2.1.0/context_guard/guard/setup.py +476 -0
- context_guard_cli-2.1.0/context_guard/guard/transaction.py +403 -0
- context_guard_cli-2.1.0/context_guard/mcp_server.py +280 -0
- context_guard_cli-2.1.0/docs/adapters/VERIFY.md +80 -0
- context_guard_cli-2.1.0/docs/adapters/antigravity/PERMISSIONS.md +58 -0
- context_guard_cli-2.1.0/docs/adapters/claude-code/PERMISSIONS.md +58 -0
- context_guard_cli-2.1.0/docs/adapters/opencode/PERMISSIONS.md +55 -0
- context_guard_cli-2.1.0/pyproject.toml +35 -0
- context_guard_cli-2.1.0/tests/__init__.py +1 -0
- context_guard_cli-2.1.0/tests/test_adapters.py +417 -0
- context_guard_cli-2.1.0/tests/test_adversarial.py +602 -0
- context_guard_cli-2.1.0/tests/test_adversarial_approve.py +376 -0
- context_guard_cli-2.1.0/tests/test_adversarial_migrate.py +457 -0
- context_guard_cli-2.1.0/tests/test_adversarial_multichange.py +347 -0
- context_guard_cli-2.1.0/tests/test_agents_md.py +70 -0
- context_guard_cli-2.1.0/tests/test_archive.py +246 -0
- context_guard_cli-2.1.0/tests/test_assets.py +255 -0
- context_guard_cli-2.1.0/tests/test_check_completion.py +197 -0
- context_guard_cli-2.1.0/tests/test_commands.py +247 -0
- context_guard_cli-2.1.0/tests/test_doctor.py +218 -0
- context_guard_cli-2.1.0/tests/test_ergonomics.py +276 -0
- context_guard_cli-2.1.0/tests/test_json_format.py +108 -0
- context_guard_cli-2.1.0/tests/test_license.py +30 -0
- context_guard_cli-2.1.0/tests/test_locking.py +254 -0
- context_guard_cli-2.1.0/tests/test_manifest.py +132 -0
- context_guard_cli-2.1.0/tests/test_mcp_server.py +197 -0
- context_guard_cli-2.1.0/tests/test_next_task_status.py +209 -0
- context_guard_cli-2.1.0/tests/test_packaging.py +287 -0
- context_guard_cli-2.1.0/tests/test_phase_docs.py +127 -0
- context_guard_cli-2.1.0/tests/test_phases.py +123 -0
- context_guard_cli-2.1.0/tests/test_pre_commit_hook.py +364 -0
- context_guard_cli-2.1.0/tests/test_publish.py +219 -0
- context_guard_cli-2.1.0/tests/test_pyproject_pin.py +60 -0
- context_guard_cli-2.1.0/tests/test_readme.py +286 -0
- context_guard_cli-2.1.0/tests/test_readme_es.py +44 -0
- context_guard_cli-2.1.0/tests/test_release.py +149 -0
- context_guard_cli-2.1.0/tests/test_setup.py +765 -0
- context_guard_cli-2.1.0/tests/test_transaction.py +197 -0
- context_guard_cli-2.1.0/tests/test_validate.py +59 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- context-guard:begin -->
|
|
2
|
+
## ACTIVE PERSISTENCE CONTRACT: context-guard
|
|
3
|
+
MANDATORY BOOTSTRAP — read before responding to anything in a project that
|
|
4
|
+
has a `.context-guard/` directory:
|
|
5
|
+
1. Read `AGENTS.md` at the project root and follow it as your state contract.
|
|
6
|
+
2. State manager binary: `cg` (or `context-guard`). Operative subcommands:
|
|
7
|
+
begin | commit | rollback | checkpoint | status | next-task | validate
|
|
8
|
+
Human-only subcommand (never run by the agent): `cg approve`.
|
|
9
|
+
If `commit` returns EXIT_APPROVAL_REQUIRED (6): stop and ask the user to
|
|
10
|
+
run `cg approve --change <name>` themselves.
|
|
11
|
+
3. Check `.context-guard/changes/*/manifest.json` for an active change and
|
|
12
|
+
act accordingly (cold start, resume via `cg status`, or report a stuck
|
|
13
|
+
lock — never fix a stuck lock by editing the manifest by hand).
|
|
14
|
+
4. Phase instructions live in `.context-guard/phases/{plan,execute,verify}.md`.
|
|
15
|
+
<!-- context-guard:end -->
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Pre-commit gate for context-guard.
|
|
3
|
+
|
|
4
|
+
Rejects commits touching more than N files when nothing shows the
|
|
5
|
+
PLAN -> EXECUTE -> VERIFY protocol was ever engaged for this context.
|
|
6
|
+
"""
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
DEFAULT_FILE_THRESHOLD = 2
|
|
14
|
+
BYPASS_LOG = ".context-guard/bypass.log"
|
|
15
|
+
GUARD_DIR = ".context-guard"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def repo_root():
|
|
19
|
+
return subprocess.check_output(
|
|
20
|
+
["git", "rev-parse", "--show-toplevel"], text=True
|
|
21
|
+
).strip()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def staged_files():
|
|
25
|
+
out = subprocess.check_output(
|
|
26
|
+
["git", "diff", "--cached", "--name-only"], text=True
|
|
27
|
+
)
|
|
28
|
+
return [f for f in out.splitlines() if f.strip()]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def manifest_paths(root):
|
|
32
|
+
"""Every manifest in the context, newest layout first.
|
|
33
|
+
|
|
34
|
+
Since the multi-change refactor state lives in
|
|
35
|
+
.context-guard/changes/{name}/manifest.json. The flat path is still read so
|
|
36
|
+
a 1.x repo that has not run `cg migrate` yet is not blocked by an upgrade
|
|
37
|
+
of the hook alone.
|
|
38
|
+
"""
|
|
39
|
+
base = os.path.join(root, ".context-guard")
|
|
40
|
+
paths = []
|
|
41
|
+
changes_dir = os.path.join(base, "changes")
|
|
42
|
+
if os.path.isdir(changes_dir):
|
|
43
|
+
for entry in sorted(os.listdir(changes_dir)):
|
|
44
|
+
if entry == "archive":
|
|
45
|
+
continue
|
|
46
|
+
candidate = os.path.join(changes_dir, entry, "manifest.json")
|
|
47
|
+
if os.path.exists(candidate):
|
|
48
|
+
paths.append(candidate)
|
|
49
|
+
flat = os.path.join(base, "manifest.json")
|
|
50
|
+
if os.path.exists(flat):
|
|
51
|
+
paths.append(flat)
|
|
52
|
+
return paths
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def load_manifests(root):
|
|
56
|
+
"""Parse every manifest, skipping the ones we cannot read.
|
|
57
|
+
|
|
58
|
+
A corrupt manifest is a context-guard problem; letting it raise here would
|
|
59
|
+
make it a git problem too and leave the user unable to commit the fix.
|
|
60
|
+
"""
|
|
61
|
+
manifests = []
|
|
62
|
+
for path in manifest_paths(root):
|
|
63
|
+
try:
|
|
64
|
+
with open(path) as f:
|
|
65
|
+
manifests.append(json.load(f))
|
|
66
|
+
except (OSError, ValueError):
|
|
67
|
+
continue
|
|
68
|
+
return manifests
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def file_threshold(manifests):
|
|
72
|
+
"""How many staged files are allowed outside the protocol.
|
|
73
|
+
|
|
74
|
+
Precedence: the environment wins, then the manifests, then the default. The
|
|
75
|
+
env var is the per-invocation escape hatch, so committed configuration must
|
|
76
|
+
not be able to override it.
|
|
77
|
+
|
|
78
|
+
Across changes the strictest configured value wins. Picking "the first one"
|
|
79
|
+
would resolve a repo-wide policy by directory order; the minimum is
|
|
80
|
+
order-independent and errs toward asking rather than toward silence.
|
|
81
|
+
"""
|
|
82
|
+
from_env = os.environ.get("CONTEXT_GUARD_FILE_THRESHOLD")
|
|
83
|
+
if from_env is not None:
|
|
84
|
+
try:
|
|
85
|
+
return int(from_env)
|
|
86
|
+
except ValueError:
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
configured = []
|
|
90
|
+
for manifest in manifests:
|
|
91
|
+
value = manifest.get("hook", {}).get("file_threshold")
|
|
92
|
+
try:
|
|
93
|
+
configured.append(int(value))
|
|
94
|
+
except (TypeError, ValueError):
|
|
95
|
+
# A typo in the manifest must not silently disable the hook.
|
|
96
|
+
continue
|
|
97
|
+
return min(configured) if configured else DEFAULT_FILE_THRESHOLD
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def out_of_scope(manifests, files):
|
|
101
|
+
"""Staged files that no executing change declared as its scope.
|
|
102
|
+
|
|
103
|
+
Advisory only, per PLAN.md F4. Blocking here would put the hook in the way
|
|
104
|
+
of the user's own flow on a field the agent fills in by hand, and a soft
|
|
105
|
+
check that hardens into a blocker is how you end up with an agent that
|
|
106
|
+
cannot commit.
|
|
107
|
+
|
|
108
|
+
Only changes actually in EXECUTE are considered: in PLAN the scope has not
|
|
109
|
+
been decided yet, so every file would be "out" of it and the warning would
|
|
110
|
+
be noise from the first commit.
|
|
111
|
+
"""
|
|
112
|
+
scopes = []
|
|
113
|
+
for manifest in manifests:
|
|
114
|
+
if manifest.get("lock_phase") != "EXECUTE":
|
|
115
|
+
continue
|
|
116
|
+
scopes.extend(manifest.get("files_in_scope") or [])
|
|
117
|
+
if not scopes:
|
|
118
|
+
# Not declared is not the same as "nothing is allowed" — the manifest
|
|
119
|
+
# ships with files_in_scope empty.
|
|
120
|
+
return []
|
|
121
|
+
|
|
122
|
+
stray = []
|
|
123
|
+
for path in files:
|
|
124
|
+
# The guard's own state is written by every phase; warning about it on
|
|
125
|
+
# every commit is how a soft check gets tuned out.
|
|
126
|
+
if path == GUARD_DIR or path.startswith(GUARD_DIR + "/"):
|
|
127
|
+
continue
|
|
128
|
+
if any(path == s or path.startswith(s.rstrip("/") + "/") for s in scopes):
|
|
129
|
+
continue
|
|
130
|
+
stray.append(path)
|
|
131
|
+
return stray
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def protocol_engaged(manifests):
|
|
135
|
+
"""True if any change is doing the work.
|
|
136
|
+
|
|
137
|
+
One engaged change is enough: the staged files belong to whichever change
|
|
138
|
+
is active, and the hook cannot tell which without guessing — the
|
|
139
|
+
alphabetical guess PLAN.md 1.3 forbids by name.
|
|
140
|
+
"""
|
|
141
|
+
for manifest in manifests:
|
|
142
|
+
txn = manifest.get("transaction", {})
|
|
143
|
+
if manifest.get("completed_phases") or txn.get("txn_status") == "in_progress":
|
|
144
|
+
return True
|
|
145
|
+
return False
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def log_bypass(root, files, reason):
|
|
149
|
+
path = os.path.join(root, ".context-guard")
|
|
150
|
+
os.makedirs(path, exist_ok=True)
|
|
151
|
+
with open(os.path.join(path, "bypass.log"), "a") as f:
|
|
152
|
+
f.write(f"{datetime.now().isoformat()}|{reason}|files={len(files)}|{','.join(files)}\n")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def main():
|
|
156
|
+
root = repo_root()
|
|
157
|
+
files = staged_files()
|
|
158
|
+
manifests = load_manifests(root)
|
|
159
|
+
|
|
160
|
+
# Advisory, and checked on every commit regardless of size: a one-file
|
|
161
|
+
# change outside the declared scope is just as worth mentioning, and this
|
|
162
|
+
# branch never affects the exit code.
|
|
163
|
+
stray = out_of_scope(manifests, files)
|
|
164
|
+
if stray:
|
|
165
|
+
print(
|
|
166
|
+
f"[context-guard] WARN: {len(stray)} staged file(s) outside "
|
|
167
|
+
f"files_in_scope: {', '.join(stray)}",
|
|
168
|
+
file=sys.stderr,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
threshold = file_threshold(manifests)
|
|
172
|
+
if len(files) <= threshold:
|
|
173
|
+
sys.exit(0)
|
|
174
|
+
|
|
175
|
+
if protocol_engaged(manifests):
|
|
176
|
+
sys.exit(0)
|
|
177
|
+
|
|
178
|
+
if os.environ.get("CONTEXT_GUARD_BYPASS") == "1":
|
|
179
|
+
reason = os.environ.get("CONTEXT_GUARD_BYPASS_REASON", "unspecified")
|
|
180
|
+
log_bypass(root, files, reason)
|
|
181
|
+
print(f"[context-guard] BYPASS recorded in {BYPASS_LOG}", file=sys.stderr)
|
|
182
|
+
sys.exit(0)
|
|
183
|
+
|
|
184
|
+
print(
|
|
185
|
+
f"[context-guard] COMMIT REJECTED: {len(files)} files staged "
|
|
186
|
+
f"(threshold={threshold}) with no phase engaged.\n"
|
|
187
|
+
f" -> Start the protocol first: cg new <change> or cg begin --phase <PHASE>.\n"
|
|
188
|
+
f" -> Escape hatch: CONTEXT_GUARD_BYPASS=1 CONTEXT_GUARD_BYPASS_REASON='...' git commit ...\n",
|
|
189
|
+
file=sys.stderr,
|
|
190
|
+
)
|
|
191
|
+
sys.exit(1)
|
|
192
|
+
|
|
193
|
+
if __name__ == "__main__":
|
|
194
|
+
main()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, v2, v2.1]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, v2, v2.1]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
18
|
+
uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python-version }}
|
|
21
|
+
- name: Install package
|
|
22
|
+
# The dev extra pulls in `build`, without which tests/test_packaging.py
|
|
23
|
+
# skips — and that file is the only proof the wheel actually carries
|
|
24
|
+
# the embedded artifacts (PLAN-2.1 F1).
|
|
25
|
+
run: pip install ".[dev]"
|
|
26
|
+
- name: Run tests
|
|
27
|
+
run: python -m unittest discover -s tests
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
# Publishing is driven by a GitHub release, not by a push: creating the
|
|
4
|
+
# release is the human act that authorises a version going out, and it is the
|
|
5
|
+
# only step of this pipeline a person performs.
|
|
6
|
+
on:
|
|
7
|
+
release:
|
|
8
|
+
types: [published]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
# A separate job, not a step, so `needs:` below is a real gate. A test step
|
|
12
|
+
# sitting above an upload step in the same job can be skipped, reordered or
|
|
13
|
+
# made non-fatal by a later edit without anything failing loudly.
|
|
14
|
+
test:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
strategy:
|
|
17
|
+
matrix:
|
|
18
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: ${{ matrix.python-version }}
|
|
25
|
+
- name: Install package
|
|
26
|
+
# The dev extra carries `build`; without it the packaging tests skip,
|
|
27
|
+
# and the check that proves the wheel actually contains its data
|
|
28
|
+
# would silently not run on the very wheel being published.
|
|
29
|
+
run: pip install ".[dev]"
|
|
30
|
+
- name: Run tests
|
|
31
|
+
run: python -m unittest discover -s tests
|
|
32
|
+
|
|
33
|
+
publish:
|
|
34
|
+
needs: test
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
# Trusted Publishing: PyPI hands out a short-lived credential to this
|
|
37
|
+
# workflow, in this repository, in this environment, and to nothing else.
|
|
38
|
+
# The alternative — an API token in a secret — is a long-lived credential
|
|
39
|
+
# that publishes from anywhere it leaks to. Binding the capability to the
|
|
40
|
+
# place the check runs is the same principle as putting `cg approve`
|
|
41
|
+
# behind the harness permission prompt rather than trusting the agent.
|
|
42
|
+
#
|
|
43
|
+
# `environment` must match the pending publisher registered on PyPI
|
|
44
|
+
# (owner fdomerlo, repository context-guard, workflow publish.yml,
|
|
45
|
+
# environment pypi). A mismatch fails as an opaque OIDC error.
|
|
46
|
+
environment: pypi
|
|
47
|
+
permissions:
|
|
48
|
+
id-token: write
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@v4
|
|
51
|
+
- name: Set up Python
|
|
52
|
+
uses: actions/setup-python@v5
|
|
53
|
+
with:
|
|
54
|
+
python-version: "3.12"
|
|
55
|
+
- name: Build wheel and sdist
|
|
56
|
+
run: |
|
|
57
|
+
pip install build
|
|
58
|
+
python -m build
|
|
59
|
+
- name: Publish to PyPI
|
|
60
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Context Guard — Agent Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Transactional state manager for AI agents via CLI and MCP. Enforces a strict
|
|
6
|
+
`PLAN → EXECUTE → VERIFY → ARCHIVE` pipeline per change, with atomic rollback.
|
|
7
|
+
State lives in `<project-root>/.context-guard/changes/{name}/manifest.json`.
|
|
8
|
+
|
|
9
|
+
## Entrypoints
|
|
10
|
+
|
|
11
|
+
- **CLI**: `cg` (short) or `context-guard` (long) — `context_guard.guard.cli:main`
|
|
12
|
+
- **MCP server**: `context-guard-mcp` — transactional tools plus read-only ones
|
|
13
|
+
(`get_status`, `next_task`, `check_completion`, `validate`)
|
|
14
|
+
|
|
15
|
+
## Multi-change
|
|
16
|
+
|
|
17
|
+
Every command accepts `--change <name>`. Omit it only when exactly one change
|
|
18
|
+
is active — with several, the command errors and asks you to be explicit; it
|
|
19
|
+
never guesses. `cg new <name>` scaffolds a change and begins PLAN. `cg list`
|
|
20
|
+
shows active changes. `cg archive` moves a finished one to `changes/archive/`.
|
|
21
|
+
`cg migrate` converts a legacy single-change or state-guard layout in place.
|
|
22
|
+
|
|
23
|
+
## Pipeline phases
|
|
24
|
+
|
|
25
|
+
Full instructions for each phase live in `.context-guard/phases/{plan,execute,
|
|
26
|
+
verify}.md`, installed into the project from the package's embedded copy —
|
|
27
|
+
load the file for the phase you are entering and follow it. Do not skip a
|
|
28
|
+
phase; `commit` rejects any transition outside this table.
|
|
29
|
+
|
|
30
|
+
| Phase | Produces | Then |
|
|
31
|
+
|---------|----------------------------------------|--------------------------------|
|
|
32
|
+
| PLAN | `objective.md`, `tasks.md` | human review, then `cg approve` |
|
|
33
|
+
| EXECUTE | code changes, `tasks.md` checked off | `check-completion` |
|
|
34
|
+
| VERIFY | `review-report.md`, `verify-report.md` | archive on approval |
|
|
35
|
+
|
|
36
|
+
## Hard gates (validated on `commit`)
|
|
37
|
+
|
|
38
|
+
- **PLAN → EXECUTE**: `objective.md` + `tasks.md` must exist and contain no
|
|
39
|
+
`[PENDING]`.
|
|
40
|
+
- **VERIFY → ARCHIVE**: `review-report.md` + `verify-report.md` must exist and
|
|
41
|
+
contain no `[PENDING]`.
|
|
42
|
+
|
|
43
|
+
`begin` on a fresh PLAN auto-scaffolds 5 markdown files in the change
|
|
44
|
+
directory, all initialized with `[PENDING]`.
|
|
45
|
+
|
|
46
|
+
## The `cg approve` step
|
|
47
|
+
|
|
48
|
+
Before running `commit --next-phase EXECUTE`, present `objective.md` and
|
|
49
|
+
`tasks.md` to the human and wait for explicit go-ahead — never advance the
|
|
50
|
+
phase unprompted. `commit` enforces this: without a recorded approval it fails
|
|
51
|
+
with `APPROVAL_REQUIRED` (code 6).
|
|
52
|
+
|
|
53
|
+
`cg approve [--change <name>] [--by <who>] [--hotfix --reason "<text>"]` records
|
|
54
|
+
it. **Never run it yourself** — ask the human to. `--by` is required: there is
|
|
55
|
+
no default, so an agent that runs it anyway cannot pass silently as the
|
|
56
|
+
environment's `$USER`. The approval is spent by the
|
|
57
|
+
commit it authorizes, so a new iteration of the plan needs a new one.
|
|
58
|
+
`--hotfix` skips PLAN and opens EXECUTE directly; it requires a reason, which
|
|
59
|
+
is persisted. The real control is your harness's permission prompt: put
|
|
60
|
+
`cg approve` on the "ask" list (see `docs/adapters/*/PERMISSIONS.md`) so a human
|
|
61
|
+
confirms it out of band, not just in the conversation.
|
|
62
|
+
|
|
63
|
+
## Exit codes (schema v3)
|
|
64
|
+
|
|
65
|
+
`0` OK · `1` GENERIC · `2` LOCK_HELD (retry) · `3` LOCK_CONTENDED (retry) ·
|
|
66
|
+
`4` VALIDATION · `5` BAD_TRANSITION (do not retry) · `6` APPROVAL_REQUIRED
|
|
67
|
+
(human-only). `--format json` is available on every command.
|
|
68
|
+
|
|
69
|
+
## Testing
|
|
70
|
+
|
|
71
|
+
- Framework: **unittest**, run: `python -m unittest discover -s tests`
|
|
72
|
+
- Tests use `tempfile.mkdtemp()` — no fixtures, no services
|
|
73
|
+
|
|
74
|
+
## Architecture notes
|
|
75
|
+
|
|
76
|
+
- `context_guard/guard/`: `paths` (change resolution), `errors`, `manifest`
|
|
77
|
+
(atomic tmp+rename), `locking` (session + write lock, stale via PID/mtime),
|
|
78
|
+
`transaction` (`begin`/`commit`/`rollback`), `commands`, `cli`.
|
|
79
|
+
- Task claims use a lease (`claim-task`/`next-task`/`doctor --fix`) for
|
|
80
|
+
multi-agent swarms working the same change concurrently.
|
|
81
|
+
- Language enforcement: artifacts must be in English; `validate` rejects
|
|
82
|
+
Spanish text.
|
|
83
|
+
- Pre-commit hook (`git config core.hooksPath .githooks`) rejects large
|
|
84
|
+
commits outside an active transaction. Threshold: `hook.file_threshold` in
|
|
85
|
+
the manifest (strictest across changes) or `CONTEXT_GUARD_FILE_THRESHOLD`,
|
|
86
|
+
which wins. Files outside `files_in_scope` warn, never block. Bypass:
|
|
87
|
+
`CONTEXT_GUARD_BYPASS=1 CONTEXT_GUARD_BYPASS_REASON='...' git commit ...`
|
|
88
|
+
— recorded in `.context-guard/bypass.log`.
|
|
89
|
+
- `files_in_scope` and `hook.file_threshold` are manual-only fields: no `cg`
|
|
90
|
+
command writes either one. To use them, edit
|
|
91
|
+
`.context-guard/changes/<name>/manifest.json` directly — `files_in_scope`
|
|
92
|
+
is a list of path prefixes, `hook` is `{"file_threshold": <int>}`. Both
|
|
93
|
+
default to unset (empty scope, threshold 2) and work fine left alone.
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## [2.1.0] - 2026-08-01
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `cg setup` installs the host adapters for Claude Code, OpenCode and
|
|
14
|
+
Antigravity. Global scope by default — one command per machine — with
|
|
15
|
+
`--project <dir>` keeping 2.1's predecessor behaviour of installing into a
|
|
16
|
+
single project for teams that commit the configuration.
|
|
17
|
+
- The phase documents and every host artifact now ship inside the package as
|
|
18
|
+
data, so nothing needs a clone of this repository to install.
|
|
19
|
+
- `cg setup --no-hooks` declines Antigravity's `PreToolUse` deny hook, which
|
|
20
|
+
is installed by default. That hook is what stops the agent from running
|
|
21
|
+
`cg approve` itself, so the file it writes is annotated in the summary with
|
|
22
|
+
what it is and how to skip it.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- `--context` defaults to the working directory on every subcommand, and
|
|
27
|
+
`--by` defaults to the OS user, so with a single active change the whole
|
|
28
|
+
human approval is `cg approve` with no flags at all.
|
|
29
|
+
- **`--by` is no longer required.** 2.0 required it, arguing that inheriting
|
|
30
|
+
the environment made an agent-run approve indistinguishable from a
|
|
31
|
+
human-run one. The flag never authenticated anyone — an agent can pass any
|
|
32
|
+
string — and requiring it put friction on the one step that must not be
|
|
33
|
+
automated. It is audit metadata: who to ask about an approval later. The
|
|
34
|
+
authentication was and remains the harness permission prompt, which the
|
|
35
|
+
Threat Model now says explicitly.
|
|
36
|
+
- Naming a change that does not exist reports
|
|
37
|
+
`FAIL|CHANGE_NOT_FOUND|<name>|available: <list>` instead of
|
|
38
|
+
`FAIL|NO_SESSION`, which read as a broken project rather than a typo. A
|
|
39
|
+
mistyped name also no longer creates the change it names: `begin` used to
|
|
40
|
+
answer a misspelling by bringing that change into being.
|
|
41
|
+
- `cg new` materialises `.context-guard/phases/*.md` into the project from the
|
|
42
|
+
packaged copy, which is what makes a globally installed slash command work
|
|
43
|
+
in a project nobody prepared. A phase file that already exists is never
|
|
44
|
+
overwritten; `cg doctor` reports the difference as INFO.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- `cg setup --host antigravity` no longer crashes on an existing
|
|
49
|
+
`hooks.json` whose shape it did not expect (`{"hooks": [...]}` raised
|
|
50
|
+
`AttributeError` with a raw traceback). The shape is validated before the
|
|
51
|
+
merge; anything unrecognised or unparseable is reported and left byte-for-
|
|
52
|
+
byte untouched, and a host that cannot be configured no longer aborts the
|
|
53
|
+
others in the same run.
|
|
54
|
+
|
|
55
|
+
### Removed
|
|
56
|
+
|
|
57
|
+
- `adapters/install.sh`, with no compatibility wrapper. Run `cg setup`
|
|
58
|
+
instead.
|
|
59
|
+
- `--with-antigravity-hook`. The deny hook is installed by
|
|
60
|
+
`cg setup --host antigravity`, since at global scope it was the only thing
|
|
61
|
+
that flag's host had to install.
|
|
62
|
+
|
|
63
|
+
### Known gaps
|
|
64
|
+
|
|
65
|
+
Deliberately out of scope for 2.1, recorded here so the decisions outlive the
|
|
66
|
+
plan that made them:
|
|
67
|
+
|
|
68
|
+
- No `get.sh` curl-pipe bootstrap; installation is `pip install` plus
|
|
69
|
+
`cg setup`.
|
|
70
|
+
- No `cg setup --uninstall`. The exact list of files each run touches is
|
|
71
|
+
printed for that reason — it is the only record of what to remove by hand.
|
|
72
|
+
- The tutorial exists in Spanish only; no English mirror yet.
|
|
73
|
+
- `cg setup` and the three host adapters are still unverified against a real
|
|
74
|
+
host session. `docs/adapters/VERIFY.md` is the manual checklist.
|
|
75
|
+
|
|
76
|
+
## [2.0.0] - 2026-07-31
|
|
77
|
+
|
|
78
|
+
### The state-guard merge
|
|
79
|
+
|
|
80
|
+
context-guard 2.0 folds state-guard into this repo: one process instead of two
|
|
81
|
+
diverging philosophies. state-guard's multi-change layout, phased
|
|
82
|
+
`plan → execute → verify` workflow, and cooperative approval model are ported
|
|
83
|
+
here on top of context-guard's atomic manifest and DAG-enforced transaction
|
|
84
|
+
core — the better architecture of the two, kept as the skeleton. state-guard
|
|
85
|
+
is retired; its repository now points here.
|
|
86
|
+
|
|
87
|
+
### Core fixes (audited bypasses closed)
|
|
88
|
+
|
|
89
|
+
- `begin` now validates `lock_phase` before starting a phase. Previously the
|
|
90
|
+
DAG was only checked on `commit`, so an agent that never committed was never
|
|
91
|
+
stopped from working a phase it was never authorized to start.
|
|
92
|
+
- Orphaned session locks (lockfile present, no metadata) now age off the
|
|
93
|
+
file's own mtime instead of deadlocking forever.
|
|
94
|
+
- A write lock held by a live process is no longer stolen by age alone; a hard
|
|
95
|
+
cap (10x the normal TTL) still recovers one abandoned by a reused PID.
|
|
96
|
+
- Task claims carry a lease; `next-task` reclaims expired ones and logs the
|
|
97
|
+
takeover, `doctor --fix` releases claims whose owning PID is gone.
|
|
98
|
+
- `release` / `release-task` now require `--agent-id` (or an explicit
|
|
99
|
+
`--force`, logged in the manifest) — an anonymous release could previously
|
|
100
|
+
free a lock or claim it did not own.
|
|
101
|
+
- Exit codes are unified across the whole CLI: `0` OK, `1` GENERIC, `2`
|
|
102
|
+
LOCK_HELD, `3` LOCK_CONTENDED, `4` VALIDATION, `5` BAD_TRANSITION, `6`
|
|
103
|
+
APPROVAL_REQUIRED. `--format json` is available on every command.
|
|
104
|
+
|
|
105
|
+
### Multi-change
|
|
106
|
+
|
|
107
|
+
- State moves from a single `.context-guard/manifest.json` to
|
|
108
|
+
`.context-guard/changes/{name}/`, so several changes can be planned,
|
|
109
|
+
executed, and locked independently in the same project.
|
|
110
|
+
- New commands: `cg new <name>`, `cg list`, `cg archive --change <name>`.
|
|
111
|
+
- `cg migrate` converts both legacy layouts (state-guard's `state.ini` and
|
|
112
|
+
context-guard 1.x's flat directory) in place, idempotently. A 1.x layout's
|
|
113
|
+
own `archive/` subdirectories are copied into `changes/archive/` too, not
|
|
114
|
+
just its live changes. Phases state-guard tracked that this pipeline does
|
|
115
|
+
not recognise (a `hotfix` bypass state, for example) land in a
|
|
116
|
+
`legacy_phases` field instead of silently corrupting `completed_phases`.
|
|
117
|
+
- Every command accepts `--change`; with several changes active and no flag,
|
|
118
|
+
commands report the ambiguity and name them — never guess the first one
|
|
119
|
+
alphabetically, the bug both predecessor repos shipped with.
|
|
120
|
+
|
|
121
|
+
### Human approval gate
|
|
122
|
+
|
|
123
|
+
- `cg approve --change <name> --by <who> [--hotfix --reason "<text>"]`
|
|
124
|
+
records a human sign-off in the manifest. `commit --next-phase EXECUTE`
|
|
125
|
+
refuses without one (`APPROVAL_REQUIRED`, exit 6). The approval is consumed
|
|
126
|
+
by the commit it authorizes, so a later iteration of the plan needs a new
|
|
127
|
+
one. `--by` is required — no default, so an agent-run approve cannot pass
|
|
128
|
+
silently as the environment's `$USER`.
|
|
129
|
+
- `--hotfix` is the audited door out of the pipeline: it requires a reason,
|
|
130
|
+
jumps straight to EXECUTE, and records PLAN as skipped rather than
|
|
131
|
+
completed.
|
|
132
|
+
- The gate is cooperative by construction — see Threat Model in the README.
|
|
133
|
+
The hard control is your harness's permission prompt on `cg approve`,
|
|
134
|
+
documented per host in `adapters/*/PERMISSIONS.md`. `approve` is
|
|
135
|
+
deliberately not exposed over MCP, for the same reason.
|
|
136
|
+
|
|
137
|
+
### Pre-commit hook, promoted
|
|
138
|
+
|
|
139
|
+
- Reads the multi-change layout (previously blind to it, and blocking every
|
|
140
|
+
large commit regardless of protocol state).
|
|
141
|
+
- File threshold is configurable via `hook.file_threshold` in the manifest or
|
|
142
|
+
`CONTEXT_GUARD_FILE_THRESHOLD` (env wins); the strictest configured value
|
|
143
|
+
applies across changes.
|
|
144
|
+
- Files staged outside `files_in_scope` on an executing change now warn, never
|
|
145
|
+
block. Output is in English. The audited bypass
|
|
146
|
+
(`CONTEXT_GUARD_BYPASS=1 CONTEXT_GUARD_BYPASS_REASON=...`) is unchanged.
|
|
147
|
+
|
|
148
|
+
### Phases and adapters
|
|
149
|
+
|
|
150
|
+
- `phases/{plan,execute,verify}.md`, ported from state-guard, translated to
|
|
151
|
+
English, trimmed, and integrated with `[PENDING]` and `cg approve`.
|
|
152
|
+
- Thin per-harness adapters in `adapters/{claude-code,opencode,antigravity}/`
|
|
153
|
+
plus `install.sh`, all installing per-project now — OpenCode and
|
|
154
|
+
Antigravity used to write into `$HOME` (a stale absolute path to the repo
|
|
155
|
+
clone for OpenCode's generated commands, a global `~/.gemini/GEMINI.md`
|
|
156
|
+
injection for Antigravity that contaminated every project on the machine).
|
|
157
|
+
Nine confirmed bugs closed in the pass: the deprecated OpenCode `tools`
|
|
158
|
+
key, a missing manifest-edit `deny` on both OpenCode and Claude Code,
|
|
159
|
+
inconsistent command names across hosts (unified on `/cg-new` /
|
|
160
|
+
`/cg-continue`), a missing Antigravity deny hook on `run_command`, and no
|
|
161
|
+
MCP server registration anywhere.
|
|
162
|
+
- `install.sh` gains `--host claude|opencode|antigravity|all`, `--with-mcp`
|
|
163
|
+
(registers `context-guard-mcp` per host; optional, every adapter works
|
|
164
|
+
without it), and `--with-antigravity-hook` (opt-in, touches user config).
|
|
165
|
+
Every config merge is idempotent and preserves what was already there;
|
|
166
|
+
the installer prints the exact list of files it touched.
|
|
167
|
+
- Each adapter documents its own permission configuration for `cg approve`
|
|
168
|
+
in `PERMISSIONS.md`; OpenCode and Antigravity are marked unverified —
|
|
169
|
+
rewritten and covered by tests that actually run `install.sh`, but never
|
|
170
|
+
driven through a real host session. `adapters/VERIFY.md` is the manual
|
|
171
|
+
checklist for closing that gap.
|
|
172
|
+
- `AGENTS.md` rewritten to under 100 lines as the single contract an agent
|
|
173
|
+
loads.
|
|
174
|
+
|
|
175
|
+
### MCP server
|
|
176
|
+
|
|
177
|
+
- Gains the four read tools promised since F3: `get_status`, `next_task`,
|
|
178
|
+
`check_completion`, `validate`, alongside the existing transactional four.
|
|
179
|
+
- `approve` is not an MCP tool — see Threat Model.
|
|
180
|
+
|
|
181
|
+
### Packaging
|
|
182
|
+
|
|
183
|
+
- `mcp` dependency pinned with an upper bound (`>=1.0.0,<2.0.0`).
|
|
184
|
+
- `requires-python = ">=3.10"`, tested in CI across 3.10–3.13.
|
|
185
|
+
- `cg` ships as a short entry point alongside `context-guard`.
|
|
186
|
+
- MIT license added.
|
|
187
|
+
- `requirements.txt` removed — it carried `mcp>=1.0.0` with no upper bound,
|
|
188
|
+
the exact drift fixed in `pyproject.toml`, and nothing in the repo
|
|
189
|
+
consumed it. `pyproject.toml` is the single source of truth.
|
|
190
|
+
- `.claude/` excluded from the sdist after a `uv build` dry run showed it
|
|
191
|
+
bundling `.claude/settings.local.json` — untracked, ignored by this
|
|
192
|
+
machine's global gitignore rather than the repo's own, carrying absolute
|
|
193
|
+
local paths.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## [1.2.0] - 2026-07-28
|
|
198
|
+
|
|
199
|
+
### 🔒 Git Hard Gate (Pre-Commit Hook)
|
|
200
|
+
|
|
201
|
+
- Implemented a Git pre-commit hook (`.githooks/pre-commit`) that rejects commits touching more than 2 files without an active `context-guard` transaction.
|
|
202
|
+
- Prevents large changes to the repository that bypass the `PLAN → EXECUTE → VERIFY` protocol.
|
|
203
|
+
- Supports an emergency bypass via `CONTEXT_GUARD_BYPASS=1`, automatically logged to `.context-guard/bypass.log`.
|
|
204
|
+
|
|
205
|
+
### 📦 Package Namespace Fix
|
|
206
|
+
|
|
207
|
+
- Renamed the source directory from `scripts/` to `context_guard/` to avoid global collisions in Python, standardizing the module name with the tool's name.
|
|
208
|
+
- Updated internal imports to use relative references (`from .X import Y`) within the `guard` submodule, decoupling it from the root package name.
|
|
209
|
+
- Updated the entry point in `pyproject.toml` to reflect the new structure: `context_guard.mcp_server:main`.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## [1.1.0] - 2026-07-28
|
|
214
|
+
|
|
215
|
+
### 🏗️ Automatic Artifact Scaffolding
|
|
216
|
+
|
|
217
|
+
- **`_scaffold_artifacts(context_path)`** in `transaction.py`:
|
|
218
|
+
- When a `PLAN` transaction starts via `begin_transaction`, five Markdown templates are auto-generated in `.context-guard/` if they do not already exist: `objective.md`, `snapshot.md`, `tasks.md`, `review-report.md`, and `verify-report.md`.
|
|
219
|
+
- Each template is initialized with the `[PENDING]` marker to guide the LLM on which fields need to be completed before advancing phase.
|
|
220
|
+
|
|
221
|
+
### 🚧 Hard Gates in `cmd_commit`
|
|
222
|
+
|
|
223
|
+
- Strict Python-side validations before authorizing phase transitions (no dependency on system prompt compliance):
|
|
224
|
+
- **`PLAN` → `EXECUTE`**: Verifies that `objective.md` and `tasks.md` exist and contain no `[PENDING]`. Otherwise returns `EXIT_VALIDATION` with a descriptive message.
|
|
225
|
+
- **`VERIFY` → `ARCHIVE`**: Verifies that `review-report.md` and `verify-report.md` exist and contain no `[PENDING]`. Otherwise returns `EXIT_VALIDATION` with a descriptive message.
|
|
226
|
+
|
|
227
|
+
### 📜 Updated MCP Docstrings (`mcp_server.py`)
|
|
228
|
+
|
|
229
|
+
- `begin_transaction`: Documents the auto-scaffolding of the 5 `.md` files in the `PLAN` phase.
|
|
230
|
+
- `commit_transaction`: Documents the strict Hard Gate validation rules per file and per phase, exposing the constraints to the LLM via the tool schema.
|
|
231
|
+
|
|
232
|
+
### ✅ Test Suite Improvements
|
|
233
|
+
|
|
234
|
+
- New test cases in `test_transaction.py`:
|
|
235
|
+
- `test_begin_valid_phase`: verifies creation of the 5 scaffold artifacts with `[PENDING]` content.
|
|
236
|
+
- `test_commit_hard_gate_plan_to_execute_pending`: validates rejection when `PLAN` files still contain markers.
|
|
237
|
+
- `test_commit_hard_gate_verify_to_archive_pending`: validates rejection when `VERIFY` files still contain markers.
|
|
238
|
+
- Updated `test_phases.py` and `test_mcp_server.py` to prepare valid artifacts before each `commit_transaction`.
|
|
239
|
+
- Full suite: **111 tests, 0 failures**.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## [1.0.0] - 2026-07-28
|
|
244
|
+
|
|
245
|
+
### 🚀 Highlights and Core Features
|
|
246
|
+
|
|
247
|
+
- **Native MCP Server (Model Context Protocol):**
|
|
248
|
+
- Exposes native tools over `stdio` transport: `begin_transaction`, `commit_transaction`, `rollback_transaction`, and `save_checkpoint`.
|
|
249
|
+
- Transparent integration with MCP clients such as Claude Desktop, Antigravity, OpenCode, and Cursor.
|
|
250
|
+
|
|
251
|
+
- **3-State Transactional Pipeline (DAG):**
|
|
252
|
+
- Strict implementation of the phase sequence: `PLAN` $\longrightarrow$ `EXECUTE` $\longrightarrow$ `VERIFY` $\longrightarrow$ `ARCHIVE`.
|
|
253
|
+
- Validates allowed transitions to prevent arbitrary phase jumps by the LLM.
|
|
254
|
+
|
|
255
|
+
- **Atomicity and Rollback Mechanism:**
|
|
256
|
+
- Automatic `manifest.json` snapshots taken on `begin_transaction`.
|
|
257
|
+
- `rollback_transaction` tool restores the exact prior state on execution failures or failed tests during the `VERIFY` phase.
|
|
258
|
+
|
|
259
|
+
- **Concurrency Control and Write Lock (OS-level):**
|
|
260
|
+
- OS-level session locks and write mutexes (`O_CREAT|O_EXCL`) to prevent race conditions (*TOCTOU*) between concurrent agents or sessions.
|
|
261
|
+
- Automatic detection and recovery of orphaned (*stale*) locks via PID verification and timestamping.
|
|
262
|
+
|
|
263
|
+
- **Per-Project Absolute Path Anchoring:**
|
|
264
|
+
- Isolated, transparent persistence inside each project's root directory (`<PROJECT_ROOT>/.context-guard/`), eliminating contamination of the home directory (`$HOME`).
|
|
265
|
+
|
|
266
|
+
- **Standard Packaging and Zero-Install Support (`uvx`):**
|
|
267
|
+
- Added `pyproject.toml` with the `hatchling` backend and the `context-guard` executable entry point.
|
|
268
|
+
- Supports one-line execution with no prior clone via `uvx git+https://github.com/fdomerlo/context-guard.git`.
|
|
269
|
+
|
|
270
|
+
- **Tool Discovery Optimization (i18n):**
|
|
271
|
+
- Docstrings and auto-summaries standardized in English to maximize language model adherence during tool discovery and selection.
|