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.
Files changed (75) hide show
  1. context_guard_cli-2.1.0/.agents/rules/context-guard.md +15 -0
  2. context_guard_cli-2.1.0/.githooks/pre-commit +194 -0
  3. context_guard_cli-2.1.0/.github/workflows/ci.yml +27 -0
  4. context_guard_cli-2.1.0/.github/workflows/publish.yml +60 -0
  5. context_guard_cli-2.1.0/.gitignore +10 -0
  6. context_guard_cli-2.1.0/AGENTS.md +93 -0
  7. context_guard_cli-2.1.0/CHANGELOG.md +271 -0
  8. context_guard_cli-2.1.0/LICENSE +21 -0
  9. context_guard_cli-2.1.0/PKG-INFO +296 -0
  10. context_guard_cli-2.1.0/README.es.md +286 -0
  11. context_guard_cli-2.1.0/README.md +283 -0
  12. context_guard_cli-2.1.0/TUTORIAL.es.md +178 -0
  13. context_guard_cli-2.1.0/context_guard/__init__.py +1 -0
  14. context_guard_cli-2.1.0/context_guard/_data/hosts/antigravity/hooks.snippet.json +16 -0
  15. context_guard_cli-2.1.0/context_guard/_data/hosts/antigravity/rules/context-guard.md +15 -0
  16. context_guard_cli-2.1.0/context_guard/_data/hosts/claude-code/commands/cg-continue.md +13 -0
  17. context_guard_cli-2.1.0/context_guard/_data/hosts/claude-code/commands/cg-new.md +11 -0
  18. context_guard_cli-2.1.0/context_guard/_data/hosts/claude-code/mcp.snippet.json +7 -0
  19. context_guard_cli-2.1.0/context_guard/_data/hosts/claude-code/settings.snippet.json +12 -0
  20. context_guard_cli-2.1.0/context_guard/_data/hosts/opencode/agent.snippet.json +7 -0
  21. context_guard_cli-2.1.0/context_guard/_data/hosts/opencode/commands/cg-continue.md +16 -0
  22. context_guard_cli-2.1.0/context_guard/_data/hosts/opencode/commands/cg-new.md +12 -0
  23. context_guard_cli-2.1.0/context_guard/_data/hosts/opencode/mcp.snippet.json +9 -0
  24. context_guard_cli-2.1.0/context_guard/_data/hosts/opencode/permissions.snippet.json +12 -0
  25. context_guard_cli-2.1.0/context_guard/_data/phases/execute.md +99 -0
  26. context_guard_cli-2.1.0/context_guard/_data/phases/plan.md +136 -0
  27. context_guard_cli-2.1.0/context_guard/_data/phases/verify.md +129 -0
  28. context_guard_cli-2.1.0/context_guard/guard/__init__.py +1 -0
  29. context_guard_cli-2.1.0/context_guard/guard/assets.py +94 -0
  30. context_guard_cli-2.1.0/context_guard/guard/cli.py +307 -0
  31. context_guard_cli-2.1.0/context_guard/guard/commands.py +811 -0
  32. context_guard_cli-2.1.0/context_guard/guard/errors.py +71 -0
  33. context_guard_cli-2.1.0/context_guard/guard/locking.py +181 -0
  34. context_guard_cli-2.1.0/context_guard/guard/manifest.py +69 -0
  35. context_guard_cli-2.1.0/context_guard/guard/migrate.py +288 -0
  36. context_guard_cli-2.1.0/context_guard/guard/paths.py +199 -0
  37. context_guard_cli-2.1.0/context_guard/guard/setup.py +476 -0
  38. context_guard_cli-2.1.0/context_guard/guard/transaction.py +403 -0
  39. context_guard_cli-2.1.0/context_guard/mcp_server.py +280 -0
  40. context_guard_cli-2.1.0/docs/adapters/VERIFY.md +80 -0
  41. context_guard_cli-2.1.0/docs/adapters/antigravity/PERMISSIONS.md +58 -0
  42. context_guard_cli-2.1.0/docs/adapters/claude-code/PERMISSIONS.md +58 -0
  43. context_guard_cli-2.1.0/docs/adapters/opencode/PERMISSIONS.md +55 -0
  44. context_guard_cli-2.1.0/pyproject.toml +35 -0
  45. context_guard_cli-2.1.0/tests/__init__.py +1 -0
  46. context_guard_cli-2.1.0/tests/test_adapters.py +417 -0
  47. context_guard_cli-2.1.0/tests/test_adversarial.py +602 -0
  48. context_guard_cli-2.1.0/tests/test_adversarial_approve.py +376 -0
  49. context_guard_cli-2.1.0/tests/test_adversarial_migrate.py +457 -0
  50. context_guard_cli-2.1.0/tests/test_adversarial_multichange.py +347 -0
  51. context_guard_cli-2.1.0/tests/test_agents_md.py +70 -0
  52. context_guard_cli-2.1.0/tests/test_archive.py +246 -0
  53. context_guard_cli-2.1.0/tests/test_assets.py +255 -0
  54. context_guard_cli-2.1.0/tests/test_check_completion.py +197 -0
  55. context_guard_cli-2.1.0/tests/test_commands.py +247 -0
  56. context_guard_cli-2.1.0/tests/test_doctor.py +218 -0
  57. context_guard_cli-2.1.0/tests/test_ergonomics.py +276 -0
  58. context_guard_cli-2.1.0/tests/test_json_format.py +108 -0
  59. context_guard_cli-2.1.0/tests/test_license.py +30 -0
  60. context_guard_cli-2.1.0/tests/test_locking.py +254 -0
  61. context_guard_cli-2.1.0/tests/test_manifest.py +132 -0
  62. context_guard_cli-2.1.0/tests/test_mcp_server.py +197 -0
  63. context_guard_cli-2.1.0/tests/test_next_task_status.py +209 -0
  64. context_guard_cli-2.1.0/tests/test_packaging.py +287 -0
  65. context_guard_cli-2.1.0/tests/test_phase_docs.py +127 -0
  66. context_guard_cli-2.1.0/tests/test_phases.py +123 -0
  67. context_guard_cli-2.1.0/tests/test_pre_commit_hook.py +364 -0
  68. context_guard_cli-2.1.0/tests/test_publish.py +219 -0
  69. context_guard_cli-2.1.0/tests/test_pyproject_pin.py +60 -0
  70. context_guard_cli-2.1.0/tests/test_readme.py +286 -0
  71. context_guard_cli-2.1.0/tests/test_readme_es.py +44 -0
  72. context_guard_cli-2.1.0/tests/test_release.py +149 -0
  73. context_guard_cli-2.1.0/tests/test_setup.py +765 -0
  74. context_guard_cli-2.1.0/tests/test_transaction.py +197 -0
  75. 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,10 @@
1
+ .context-guard/
2
+ __pycache__/
3
+ *.pyc
4
+ *.tmp
5
+ *.zip
6
+ .venv/
7
+ dist/
8
+ PLAN.md
9
+ PLAN-2.1.md
10
+ RUNBOOK_HUMANO.md
@@ -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.