context-guard-cli 2.4.0__tar.gz → 2.5.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.4.0 → context_guard_cli-2.5.0}/.gitignore +2 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/CHANGELOG.md +22 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/PKG-INFO +4 -4
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/README.es.md +2 -2
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/README.md +2 -2
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/TUTORIAL.es.md +2 -2
- context_guard_cli-2.5.0/context_guard/_data/hosts/cursor/mcp.snippet.json +7 -0
- context_guard_cli-2.5.0/context_guard/_data/hosts/cursor/rules/context-guard.mdc +20 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/guard/assets.py +3 -3
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/guard/cli.py +2 -2
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/guard/commands.py +4 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/guard/setup.py +78 -6
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/docs/adapters/VERIFY.md +2 -1
- context_guard_cli-2.5.0/docs/adapters/cursor/PERMISSIONS.md +42 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/pyproject.toml +1 -1
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_adapters.py +23 -2
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_assets.py +7 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_packaging.py +2 -2
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_release.py +5 -8
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_setup.py +61 -3
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/.agents/rules/context-guard.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/.githooks/pre-commit +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/.github/workflows/ci.yml +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/.github/workflows/publish.yml +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/AGENTS.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/LICENSE +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/__init__.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/antigravity/hooks.snippet.json +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/antigravity/rules/context-guard.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/antigravity/skills/context-guard/SKILL.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/claude-code/commands/cg-continue.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/claude-code/commands/cg-new.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/claude-code/mcp.snippet.json +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/claude-code/settings.snippet.json +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/opencode/agent.snippet.json +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/opencode/commands/cg-continue.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/opencode/commands/cg-new.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/opencode/mcp.snippet.json +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/opencode/permissions.snippet.json +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/_data/phases/execute.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/_data/phases/plan.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/_data/phases/verify.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/guard/__init__.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/guard/errors.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/guard/locking.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/guard/manifest.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/guard/migrate.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/guard/paths.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/guard/plan_import.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/guard/transaction.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/context_guard/mcp_server.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/docs/adapters/antigravity/PERMISSIONS.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/docs/adapters/claude-code/PERMISSIONS.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/docs/adapters/opencode/PERMISSIONS.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/__init__.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/fixtures/plan_en.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/fixtures/plan_es.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/fixtures/plan_no_phases.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/fixtures/plan_sentinel.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/fixtures/plan_sparse.md +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_adversarial.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_adversarial_approve.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_adversarial_migrate.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_adversarial_multichange.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_agents_md.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_archive.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_check_completion.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_commands.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_doctor.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_ergonomics.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_from_plan.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_json_format.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_license.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_locking.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_manifest.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_mcp_server.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_next_task_status.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_phase_docs.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_phases.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_plan_import.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_pre_commit_hook.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_publish.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_pyproject_pin.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_readme.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_readme_es.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_transaction.py +0 -0
- {context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/tests/test_validate.py +0 -0
|
@@ -6,6 +6,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [2.5.0] - 2026-09-03
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Support for Cursor as a host adapter: `cg setup --host cursor` installs
|
|
14
|
+
`.cursor/rules/context-guard.mdc` (both globally in `~/.cursor/rules` and
|
|
15
|
+
per-project) and optionally registers `context-guard-mcp` in `.cursor/mcp.json`
|
|
16
|
+
via `--with-mcp`.
|
|
17
|
+
- `cg new` automatically detects Cursor and materialises `.cursor/rules/context-guard.mdc`
|
|
18
|
+
in newly created changes.
|
|
19
|
+
- Documentation for Cursor's cooperative approval gate model in
|
|
20
|
+
`docs/adapters/cursor/PERMISSIONS.md`.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Unified Gemini/Antigravity global skill installation path to
|
|
25
|
+
`~/.gemini/config/skills/context-guard/SKILL.md` (the canonical machine-local
|
|
26
|
+
configuration root shared across Gemini CLI, IDE, Desktop, and ACP).
|
|
27
|
+
This fixes broken symlinks when `~/.gemini/config/skills/` was not yet created.
|
|
28
|
+
Any legacy owned skill file at `~/.gemini/antigravity-cli/skills/context-guard/SKILL.md`
|
|
29
|
+
is cleaned up automatically on setup.
|
|
30
|
+
|
|
9
31
|
## [2.4.0] - 2026-08-06
|
|
10
32
|
|
|
11
33
|
The bridge between a plan and a change was copy-paste. A phased `PLAN-N.md`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: context-guard-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: The transactional memory layer for AI coding agents — your context survives crashes, compaction, and session loss
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -95,7 +95,7 @@ context-guard-cli`**: that installs into whichever venv is currently active —
|
|
|
95
95
|
a single project's, if you happen to be inside one — not onto the machine,
|
|
96
96
|
which silently defeats the point of a global `cg setup`.
|
|
97
97
|
|
|
98
|
-
`cg setup` detects Claude Code, OpenCode and
|
|
98
|
+
`cg setup` detects Claude Code, OpenCode, Antigravity and Cursor, installs the slash
|
|
99
99
|
commands, and puts `cg approve` behind each one's permission prompt — see
|
|
100
100
|
[Adapters](#adapters-and-permission-configuration). It prints every file it
|
|
101
101
|
touched, and running it again changes nothing.
|
|
@@ -307,7 +307,7 @@ an open transaction). Activate it once per clone with
|
|
|
307
307
|
## Adapters and permission configuration
|
|
308
308
|
|
|
309
309
|
Thin, per-harness wrappers ship inside the package under
|
|
310
|
-
`context_guard/_data/hosts/{claude-code,opencode,antigravity}/` — each points
|
|
310
|
+
`context_guard/_data/hosts/{claude-code,opencode,antigravity,cursor}/` — each points
|
|
311
311
|
at `phases/{plan,execute,verify}.md` rather than duplicating them. How to put
|
|
312
312
|
`cg approve` behind each harness's permission prompt is documented in
|
|
313
313
|
[docs/adapters/](docs/adapters/), one `PERMISSIONS.md` per host, alongside the
|
|
@@ -80,7 +80,7 @@ en ese momento — el de un proyecto puntual, si estás parado adentro de
|
|
|
80
80
|
uno — no en la máquina, lo que anula en silencio el sentido de un `cg setup`
|
|
81
81
|
global.
|
|
82
82
|
|
|
83
|
-
`cg setup` detecta Claude Code, OpenCode y
|
|
83
|
+
`cg setup` detecta Claude Code, OpenCode, Antigravity y Cursor, instala los slash
|
|
84
84
|
commands, y deja `cg approve` detrás del permission prompt de cada uno — ver
|
|
85
85
|
[Adapters](#adapters-y-configuración-de-permisos). Imprime cada archivo que
|
|
86
86
|
tocó, y correrlo de nuevo no cambia nada.
|
|
@@ -297,7 +297,7 @@ fase completada o una transacción abierta). Activalo una vez por clon con
|
|
|
297
297
|
## Adapters y configuración de permisos
|
|
298
298
|
|
|
299
299
|
Dentro del paquete, en `context_guard/_data/hosts/{claude-code,opencode,
|
|
300
|
-
antigravity}/`, viven wrappers finos por harness — cada uno apunta a
|
|
300
|
+
antigravity,cursor}/`, viven wrappers finos por harness — cada uno apunta a
|
|
301
301
|
`phases/{plan,execute,verify}.md` en vez de duplicarlos. Cómo poner
|
|
302
302
|
`cg approve` detrás del permission prompt de cada harness está documentado en
|
|
303
303
|
[docs/adapters/](docs/adapters/), un `PERMISSIONS.md` por host, junto con el
|
|
@@ -82,7 +82,7 @@ context-guard-cli`**: that installs into whichever venv is currently active —
|
|
|
82
82
|
a single project's, if you happen to be inside one — not onto the machine,
|
|
83
83
|
which silently defeats the point of a global `cg setup`.
|
|
84
84
|
|
|
85
|
-
`cg setup` detects Claude Code, OpenCode and
|
|
85
|
+
`cg setup` detects Claude Code, OpenCode, Antigravity and Cursor, installs the slash
|
|
86
86
|
commands, and puts `cg approve` behind each one's permission prompt — see
|
|
87
87
|
[Adapters](#adapters-and-permission-configuration). It prints every file it
|
|
88
88
|
touched, and running it again changes nothing.
|
|
@@ -294,7 +294,7 @@ an open transaction). Activate it once per clone with
|
|
|
294
294
|
## Adapters and permission configuration
|
|
295
295
|
|
|
296
296
|
Thin, per-harness wrappers ship inside the package under
|
|
297
|
-
`context_guard/_data/hosts/{claude-code,opencode,antigravity}/` — each points
|
|
297
|
+
`context_guard/_data/hosts/{claude-code,opencode,antigravity,cursor}/` — each points
|
|
298
298
|
at `phases/{plan,execute,verify}.md` rather than duplicating them. How to put
|
|
299
299
|
`cg approve` behind each harness's permission prompt is documented in
|
|
300
300
|
[docs/adapters/](docs/adapters/), one `PERMISSIONS.md` per host, alongside the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Tutorial: memoria persistente para tu asistente de programación con context-guard
|
|
2
2
|
|
|
3
|
-
*Para personas que usan asistentes de IA (Claude Code, OpenCode, Antigravity) y quieren que no se olviden de lo que estaban haciendo. No hace falta ser experto: si sabés abrir una terminal y copiar comandos, podés seguir esta guía.*
|
|
3
|
+
*Para personas que usan asistentes de IA (Claude Code, OpenCode, Antigravity, Cursor) y quieren que no se olviden de lo que estaban haciendo. No hace falta ser experto: si sabés abrir una terminal y copiar comandos, podés seguir esta guía.*
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -27,7 +27,7 @@ Una aclaración honesta, porque este proyecto se toma en serio la honestidad: co
|
|
|
27
27
|
- **Una computadora con terminal.** macOS, Linux, o Windows con WSL.
|
|
28
28
|
- **Python 3.10 o más nuevo.** Verificalo con `python3 --version`. Si no lo tenés, instalalo desde [python.org](https://www.python.org/downloads/).
|
|
29
29
|
- **git.** Verificalo con `git --version`.
|
|
30
|
-
- **Un asistente de programación instalado**: Claude Code, OpenCode o
|
|
30
|
+
- **Un asistente de programación instalado**: Claude Code, OpenCode, Antigravity o Cursor. Cualquiera funciona; los comandos del asistente son los mismos.
|
|
31
31
|
- **Un proyecto** donde trabajar. Puede ser uno existente o una carpeta nueva; solo necesita ser un repositorio git (si no lo es, entrá a la carpeta y corré `git init`).
|
|
32
32
|
|
|
33
33
|
---
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Active persistence contract and phase discipline for context-guard
|
|
3
|
+
globs:
|
|
4
|
+
alwaysApply: true
|
|
5
|
+
---
|
|
6
|
+
<!-- context-guard:begin -->
|
|
7
|
+
## ACTIVE PERSISTENCE CONTRACT: context-guard
|
|
8
|
+
MANDATORY BOOTSTRAP — read before responding to anything in a project that
|
|
9
|
+
has a `.context-guard/` directory:
|
|
10
|
+
1. Read `AGENTS.md` at the project root and follow it as your state contract.
|
|
11
|
+
2. State manager binary: `cg` (or `context-guard`). Operative subcommands:
|
|
12
|
+
begin | commit | rollback | checkpoint | status | next-task | validate
|
|
13
|
+
Human-only subcommand (never run by the agent): `cg approve`.
|
|
14
|
+
If `commit` returns EXIT_APPROVAL_REQUIRED (6): stop and ask the user to
|
|
15
|
+
run `cg approve --change <name>` themselves.
|
|
16
|
+
3. Check `.context-guard/changes/*/manifest.json` for an active change and
|
|
17
|
+
act accordingly (cold start, resume via `cg status`, or report a stuck
|
|
18
|
+
lock — never fix a stuck lock by editing the manifest by hand).
|
|
19
|
+
4. Phase instructions live in `.context-guard/phases/{plan,execute,verify}.md`.
|
|
20
|
+
<!-- context-guard:end -->
|
|
@@ -17,7 +17,7 @@ from .errors import EXIT_GENERIC, GuardError
|
|
|
17
17
|
|
|
18
18
|
PHASES = ("plan", "execute", "verify")
|
|
19
19
|
|
|
20
|
-
HOSTS = ("claude-code", "opencode", "antigravity")
|
|
20
|
+
HOSTS = ("claude-code", "opencode", "antigravity", "cursor")
|
|
21
21
|
|
|
22
22
|
# Which snippets each host ships, keyed by the short name callers pass. The
|
|
23
23
|
# file is always "<name>.snippet.json"; the mapping exists to be an allowlist,
|
|
@@ -26,6 +26,7 @@ SNIPPETS = {
|
|
|
26
26
|
"claude-code": ("settings", "mcp"),
|
|
27
27
|
"opencode": ("agent", "permissions", "mcp"),
|
|
28
28
|
"antigravity": ("hooks",),
|
|
29
|
+
"cursor": ("mcp",),
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
|
|
@@ -62,8 +63,7 @@ def iter_host_files(host):
|
|
|
62
63
|
`relpath` is relative to the host's directory and always uses forward
|
|
63
64
|
slashes, because callers join it onto a target directory to write the file
|
|
64
65
|
out. Raises for an unknown host instead of yielding nothing: a `cg setup`
|
|
65
|
-
that installs zero files and exits 0 looks exactly like success.
|
|
66
|
-
"""
|
|
66
|
+
that installs zero files and exits 0 looks exactly like success."""
|
|
67
67
|
if host not in HOSTS:
|
|
68
68
|
raise AssetNotFoundError(f"unknown host '{host}' (expected one of {', '.join(HOSTS)})")
|
|
69
69
|
|
|
@@ -131,7 +131,7 @@ def parse_args(argv=None):
|
|
|
131
131
|
p_new = subparsers.add_parser("new")
|
|
132
132
|
p_new.add_argument("--context", default=".")
|
|
133
133
|
p_new.add_argument("name")
|
|
134
|
-
p_new.add_argument("--host", choices=["claude", "opencode", "antigravity"],
|
|
134
|
+
p_new.add_argument("--host", choices=["claude", "opencode", "antigravity", "cursor"],
|
|
135
135
|
default=None,
|
|
136
136
|
help="Also materialise this host's workspace files "
|
|
137
137
|
"(Antigravity's rule file). Detected automatically "
|
|
@@ -148,7 +148,7 @@ def parse_args(argv=None):
|
|
|
148
148
|
# setup takes no --context: it configures hosts, not a change. --project
|
|
149
149
|
# opts back into 2.0's per-project install for teams committing the config.
|
|
150
150
|
p_setup = subparsers.add_parser("setup")
|
|
151
|
-
p_setup.add_argument("--host", choices=["claude", "opencode", "antigravity", "all"],
|
|
151
|
+
p_setup.add_argument("--host", choices=["claude", "opencode", "antigravity", "cursor", "all"],
|
|
152
152
|
default="all")
|
|
153
153
|
p_setup.add_argument("--with-mcp", action="store_true",
|
|
154
154
|
help="Also register the context-guard-mcp server. "
|
|
@@ -37,8 +37,10 @@ from .plan_import import (
|
|
|
37
37
|
)
|
|
38
38
|
from .setup import (
|
|
39
39
|
antigravity_detected,
|
|
40
|
+
cursor_detected,
|
|
40
41
|
diverged_phases,
|
|
41
42
|
materialise_antigravity_rule,
|
|
43
|
+
materialise_cursor_rule,
|
|
42
44
|
materialise_phases,
|
|
43
45
|
run_setup,
|
|
44
46
|
)
|
|
@@ -143,6 +145,8 @@ def cmd_new(context, change, host=None):
|
|
|
143
145
|
materialise_phases(context)
|
|
144
146
|
if host == "antigravity" or (host is None and antigravity_detected()):
|
|
145
147
|
materialise_antigravity_rule(context)
|
|
148
|
+
if host == "cursor" or (host is None and cursor_detected()):
|
|
149
|
+
materialise_cursor_rule(context)
|
|
146
150
|
|
|
147
151
|
return CommandResult(f"SUCCESS|CHANGE_CREATED|{name}|phase=PLAN", EXIT_OK)
|
|
148
152
|
|
|
@@ -27,6 +27,7 @@ HOST_DIRS = {
|
|
|
27
27
|
"claude": "claude-code",
|
|
28
28
|
"opencode": "opencode",
|
|
29
29
|
"antigravity": "antigravity",
|
|
30
|
+
"cursor": "cursor",
|
|
30
31
|
}
|
|
31
32
|
VALID_HOSTS = tuple(HOST_DIRS) + ("all",)
|
|
32
33
|
|
|
@@ -214,12 +215,12 @@ def _install_opencode(root, with_mcp, global_scope):
|
|
|
214
215
|
return touched
|
|
215
216
|
|
|
216
217
|
|
|
217
|
-
#
|
|
218
|
-
#
|
|
219
|
-
#
|
|
220
|
-
#
|
|
218
|
+
# ~/.gemini/config/ is the unified configuration root for all Gemini tools
|
|
219
|
+
# (CLI, IDE, ACP, Desktop). Hooks live at ~/.gemini/config/hooks.json and
|
|
220
|
+
# global skills at ~/.gemini/config/skills/<name>/SKILL.md.
|
|
221
|
+
# (~/.gemini/antigravity-cli/skills is a symlink pointing to ~/.gemini/config/skills).
|
|
221
222
|
ANTIGRAVITY_GLOBAL_ROOT = (".gemini", "config")
|
|
222
|
-
ANTIGRAVITY_SKILL_REL = (".gemini", "
|
|
223
|
+
ANTIGRAVITY_SKILL_REL = (".gemini", "config", "skills", "context-guard", "SKILL.md")
|
|
223
224
|
|
|
224
225
|
# Both artifacts we write into a user's tree as whole files carry this marker,
|
|
225
226
|
# so a later run can tell its own output from a file of the same name the user
|
|
@@ -320,12 +321,25 @@ def _install_antigravity(root, global_scope, no_hooks=False):
|
|
|
320
321
|
skill_text = _embedded_antigravity_file("skills/context-guard/SKILL.md")
|
|
321
322
|
if skill_text is not None:
|
|
322
323
|
skill_rel = os.path.join(*ANTIGRAVITY_SKILL_REL)
|
|
323
|
-
|
|
324
|
+
skill_dest = os.path.join(root, skill_rel)
|
|
325
|
+
written, skip = _write_owned(skill_dest, skill_text)
|
|
324
326
|
if written:
|
|
325
327
|
touched.append(skill_rel.replace(os.sep, "/"))
|
|
326
328
|
if skip:
|
|
327
329
|
skips.append(skip)
|
|
328
330
|
|
|
331
|
+
# Legacy skill path cleanup if it was installed as a real file previously
|
|
332
|
+
legacy_rel = os.path.join(".gemini", "antigravity-cli", "skills", "context-guard", "SKILL.md")
|
|
333
|
+
legacy_path = os.path.join(root, legacy_rel)
|
|
334
|
+
if (os.path.exists(legacy_path) and
|
|
335
|
+
os.path.realpath(legacy_path) != os.path.realpath(skill_dest)):
|
|
336
|
+
try:
|
|
337
|
+
with open(legacy_path, "r", encoding="utf-8") as f:
|
|
338
|
+
if OWNERSHIP_MARKER in f.read():
|
|
339
|
+
os.remove(legacy_path)
|
|
340
|
+
except OSError:
|
|
341
|
+
pass
|
|
342
|
+
|
|
329
343
|
# `--no-hooks` declines the enforcement, not the discovery. A user who
|
|
330
344
|
# does not want the deny hook still wants their agent to know the tool
|
|
331
345
|
# is there.
|
|
@@ -368,6 +382,36 @@ def _install_antigravity(root, global_scope, no_hooks=False):
|
|
|
368
382
|
return touched, None, skips
|
|
369
383
|
|
|
370
384
|
|
|
385
|
+
def _install_cursor(root, with_mcp, global_scope):
|
|
386
|
+
"""Cursor adapter installs .cursor/rules/context-guard.mdc (in both global
|
|
387
|
+
and project scopes) and optionally registers context-guard-mcp in .cursor/mcp.json.
|
|
388
|
+
"""
|
|
389
|
+
touched = []
|
|
390
|
+
skips = []
|
|
391
|
+
|
|
392
|
+
for relpath, text in iter_host_files("cursor"):
|
|
393
|
+
if relpath == "rules/context-guard.mdc":
|
|
394
|
+
rule_rel = os.path.join(".cursor", "rules", "context-guard.mdc")
|
|
395
|
+
written, skip = _write_owned(os.path.join(root, rule_rel), text)
|
|
396
|
+
if written:
|
|
397
|
+
touched.append(rule_rel.replace(os.sep, "/"))
|
|
398
|
+
if skip:
|
|
399
|
+
skips.append(skip)
|
|
400
|
+
break
|
|
401
|
+
|
|
402
|
+
if with_mcp:
|
|
403
|
+
mcp_rel = os.path.join(".cursor", "mcp.json")
|
|
404
|
+
mcp_path = os.path.join(root, mcp_rel)
|
|
405
|
+
snippet = json.loads(read_snippet("cursor", "mcp"))
|
|
406
|
+
cfg = _read_json(mcp_path) or {}
|
|
407
|
+
cfg.setdefault("mcpServers", {}).setdefault(
|
|
408
|
+
"context-guard", snippet["mcpServers"]["context-guard"])
|
|
409
|
+
_write_json(mcp_path, cfg)
|
|
410
|
+
touched.append(mcp_rel.replace(os.sep, "/"))
|
|
411
|
+
|
|
412
|
+
return touched, skips
|
|
413
|
+
|
|
414
|
+
|
|
371
415
|
# ---------------------------------------------------------------------------
|
|
372
416
|
# Detection
|
|
373
417
|
# ---------------------------------------------------------------------------
|
|
@@ -398,6 +442,9 @@ def _detected(host, home):
|
|
|
398
442
|
or os.path.isdir(os.path.join(home, ".gemini", "antigravity-cli"))
|
|
399
443
|
or _on_path("antigravity")
|
|
400
444
|
or _on_path("agy"))
|
|
445
|
+
if host == "cursor":
|
|
446
|
+
return (os.path.isdir(os.path.join(home, ".cursor"))
|
|
447
|
+
or _on_path("cursor"))
|
|
401
448
|
return False
|
|
402
449
|
|
|
403
450
|
|
|
@@ -465,6 +512,15 @@ def run_setup(host="all", with_mcp=False, project=None, no_hooks=False):
|
|
|
465
512
|
else:
|
|
466
513
|
lines.append(" -> Antigravity: skill installed, deny hook merged "
|
|
467
514
|
"into ~/.gemini/config/hooks.json")
|
|
515
|
+
elif name == "cursor":
|
|
516
|
+
cursor_touched, cursor_skips = _install_cursor(
|
|
517
|
+
root, with_mcp, global_scope)
|
|
518
|
+
touched += cursor_touched
|
|
519
|
+
skips += cursor_skips
|
|
520
|
+
if with_mcp:
|
|
521
|
+
lines.append(" -> Cursor: rule installed in .cursor/rules, MCP registered in .cursor/mcp.json")
|
|
522
|
+
else:
|
|
523
|
+
lines.append(" -> Cursor: rule installed in .cursor/rules (cooperative approval gate)")
|
|
468
524
|
|
|
469
525
|
for name in HOST_DIRS:
|
|
470
526
|
if name in selected:
|
|
@@ -549,6 +605,22 @@ def antigravity_detected():
|
|
|
549
605
|
return _detected("antigravity", _home())
|
|
550
606
|
|
|
551
607
|
|
|
608
|
+
def materialise_cursor_rule(context):
|
|
609
|
+
"""Write Cursor rule file, unless the project already has one."""
|
|
610
|
+
path = os.path.join(context, ".cursor", "rules", "context-guard.mdc")
|
|
611
|
+
if os.path.exists(path):
|
|
612
|
+
return []
|
|
613
|
+
for relpath, text in iter_host_files("cursor"):
|
|
614
|
+
if relpath == "rules/context-guard.mdc":
|
|
615
|
+
_write_text(path, text)
|
|
616
|
+
return [path]
|
|
617
|
+
return []
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def cursor_detected():
|
|
621
|
+
return _detected("cursor", _home())
|
|
622
|
+
|
|
623
|
+
|
|
552
624
|
def diverged_phases(context):
|
|
553
625
|
"""Phase files that exist but differ from the embedded copy."""
|
|
554
626
|
from .assets import PHASES, get_phase
|
|
@@ -17,7 +17,7 @@ that has to work first for 4-5 to mean anything.
|
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
cd /path/to/toy-project
|
|
20
|
-
cg setup --host <claude|opencode|antigravity>
|
|
20
|
+
cg setup --host <claude|opencode|antigravity|cursor>
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Confirm the files land where the tests say they should:
|
|
@@ -88,3 +88,4 @@ today.
|
|
|
88
88
|
- [x] Claude Code — verified by: fdomerlo on: 2026-08-02
|
|
89
89
|
- [x] OpenCode — verified by: fdomerlo on: 2026-08-02
|
|
90
90
|
- [x] Antigravity — verified by: fdomerlo on: 2026-08-02
|
|
91
|
+
- [ ] Cursor — unverified
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Cursor — permission configuration
|
|
2
|
+
|
|
3
|
+
`cg approve` is the only command in context-guard that a human is supposed to
|
|
4
|
+
run. Everything else the agent drives itself.
|
|
5
|
+
|
|
6
|
+
## What this buys you
|
|
7
|
+
|
|
8
|
+
The approval gate inside `cg` is **cooperative**: `commit --next-phase EXECUTE`
|
|
9
|
+
refuses without a recorded approval (exit code 6), but an agent with a shell
|
|
10
|
+
can run `cg approve` itself. A confirmation that happens outside the agent's
|
|
11
|
+
process is the only hard control in the model — see PLAN.md 0.6.
|
|
12
|
+
|
|
13
|
+
## How to configure it
|
|
14
|
+
|
|
15
|
+
Cursor does not currently provide a per-command shell deny hook (like Antigravity's
|
|
16
|
+
`PreToolUse` hook in `hooks.json`) or a fine-grained command permission list (like
|
|
17
|
+
Claude Code's `permissions.ask`).
|
|
18
|
+
|
|
19
|
+
Therefore, enforcement in Cursor is **cooperative**:
|
|
20
|
+
|
|
21
|
+
1. **Keep terminal confirmation enabled.** Leave terminal command confirmation
|
|
22
|
+
enabled in Cursor settings so terminal commands — `cg approve` included —
|
|
23
|
+
surface for human confirmation before execution.
|
|
24
|
+
2. **Keep the workspace rule installed.** `.cursor/rules/context-guard.mdc`
|
|
25
|
+
(installed by `cg setup --host cursor`) instructs the agent that `cg approve`
|
|
26
|
+
is human-only and directs it to pause and request human approval when `commit`
|
|
27
|
+
returns exit code 6.
|
|
28
|
+
|
|
29
|
+
If auto-run terminal commands is enabled in Cursor, understand that enforcement
|
|
30
|
+
becomes purely cooperative, and the manifest's `approval_history` records what
|
|
31
|
+
the agent decided rather than what you explicitly gated.
|
|
32
|
+
|
|
33
|
+
## MCP registration
|
|
34
|
+
|
|
35
|
+
`cg setup --host cursor --with-mcp` automatically registers `context-guard-mcp`
|
|
36
|
+
in `.cursor/mcp.json`. Registration is optional; `cg` works completely via CLI
|
|
37
|
+
without MCP.
|
|
38
|
+
|
|
39
|
+
## Unverified
|
|
40
|
+
|
|
41
|
+
**This adapter is unverified.** It is covered by static tests and has not been
|
|
42
|
+
tested in an interactive end-to-end Cursor session.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "context-guard-cli"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.5.0"
|
|
8
8
|
description = "The transactional memory layer for AI coding agents — your context survives crashes, compaction, and session loss"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -95,7 +95,7 @@ class TestPermissionDocsPerHarness(unittest.TestCase):
|
|
|
95
95
|
it ships the cooperative half and calls it enforcement.
|
|
96
96
|
"""
|
|
97
97
|
|
|
98
|
-
HOSTS = ("claude-code", "opencode", "antigravity")
|
|
98
|
+
HOSTS = ("claude-code", "opencode", "antigravity", "cursor")
|
|
99
99
|
|
|
100
100
|
def _permissions_doc(self, host):
|
|
101
101
|
path = os.path.join(DOCS_DIR, host, "PERMISSIONS.md")
|
|
@@ -128,7 +128,7 @@ class TestPermissionDocsPerHarness(unittest.TestCase):
|
|
|
128
128
|
"""PLAN.md backlog: the OpenCode and Antigravity adapters were never run
|
|
129
129
|
against a real host. Documenting them as if they were is the kind of
|
|
130
130
|
claim the audits exist to falsify."""
|
|
131
|
-
for host in ("opencode", "antigravity"):
|
|
131
|
+
for host in ("opencode", "antigravity", "cursor"):
|
|
132
132
|
text = self._permissions_doc(host).lower()
|
|
133
133
|
self.assertIn("unverified", text)
|
|
134
134
|
|
|
@@ -416,3 +416,24 @@ class TestVerifyChecklist(unittest.TestCase):
|
|
|
416
416
|
at all."""
|
|
417
417
|
self.assertRegex(self.text, r"- \[[ x]\]")
|
|
418
418
|
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
CURSOR_DIR = os.path.join(HOSTS_DIR, "cursor")
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
class TestCursorAdapter(unittest.TestCase):
|
|
425
|
+
RULE_PATH = os.path.join(CURSOR_DIR, "rules", "context-guard.mdc")
|
|
426
|
+
MCP_PATH = os.path.join(CURSOR_DIR, "mcp.snippet.json")
|
|
427
|
+
|
|
428
|
+
def test_rule_file_exists(self):
|
|
429
|
+
self.assertTrue(os.path.exists(self.RULE_PATH), "cursor rule file missing")
|
|
430
|
+
with open(self.RULE_PATH, "r", encoding="utf-8") as f:
|
|
431
|
+
content = f.read()
|
|
432
|
+
self.assertIn("cg approve", content)
|
|
433
|
+
self.assertIn("AGENTS.md", content)
|
|
434
|
+
|
|
435
|
+
def test_mcp_snippet_is_valid_json(self):
|
|
436
|
+
self.assertTrue(os.path.exists(self.MCP_PATH), "cursor mcp snippet missing")
|
|
437
|
+
with open(self.MCP_PATH, "r", encoding="utf-8") as f:
|
|
438
|
+
data = json.load(f)
|
|
439
|
+
self.assertEqual(data["mcpServers"]["context-guard"]["command"], "context-guard-mcp")
|
|
@@ -41,6 +41,8 @@ EXPECTED_DATA_FILES = {
|
|
|
41
41
|
"hosts/antigravity/rules/context-guard.md",
|
|
42
42
|
"hosts/antigravity/skills/context-guard/SKILL.md",
|
|
43
43
|
"hosts/antigravity/hooks.snippet.json",
|
|
44
|
+
"hosts/cursor/rules/context-guard.mdc",
|
|
45
|
+
"hosts/cursor/mcp.snippet.json",
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
EXPECTED_HOST_FILES = {
|
|
@@ -62,6 +64,10 @@ EXPECTED_HOST_FILES = {
|
|
|
62
64
|
"skills/context-guard/SKILL.md",
|
|
63
65
|
"hooks.snippet.json",
|
|
64
66
|
},
|
|
67
|
+
"cursor": {
|
|
68
|
+
"rules/context-guard.mdc",
|
|
69
|
+
"mcp.snippet.json",
|
|
70
|
+
},
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
SNIPPETS = (
|
|
@@ -71,6 +77,7 @@ SNIPPETS = (
|
|
|
71
77
|
("opencode", "permissions"),
|
|
72
78
|
("opencode", "mcp"),
|
|
73
79
|
("antigravity", "hooks"),
|
|
80
|
+
("cursor", "mcp"),
|
|
74
81
|
)
|
|
75
82
|
|
|
76
83
|
|
|
@@ -130,7 +130,7 @@ for name in ("plan", "execute", "verify"):
|
|
|
130
130
|
for host, snippet in %(snippets)r:
|
|
131
131
|
out["snippets"]["%%s/%%s" %% (host, snippet)] = assets.read_snippet(host, snippet)
|
|
132
132
|
out["hosts"] = {h: sorted(r for r, _ in assets.iter_host_files(h))
|
|
133
|
-
for h in ("claude-code", "opencode", "antigravity")}
|
|
133
|
+
for h in ("claude-code", "opencode", "antigravity", "cursor")}
|
|
134
134
|
json.dump(out, sys.stdout)
|
|
135
135
|
"""
|
|
136
136
|
|
|
@@ -238,7 +238,7 @@ json.dump(out, sys.stdout)
|
|
|
238
238
|
# three hosts must each end up with a *discoverable*
|
|
239
239
|
# entry point. Antigravity's is this skill; the hook
|
|
240
240
|
# above is enforcement, which nobody discovers.
|
|
241
|
-
".gemini/
|
|
241
|
+
".gemini/config/skills/context-guard/SKILL.md"):
|
|
242
242
|
with self.subTest(path=relpath):
|
|
243
243
|
self.assertTrue(os.path.exists(os.path.join(home, relpath)),
|
|
244
244
|
f"{relpath} was not configured by cg setup")
|
|
@@ -23,18 +23,15 @@ SPANISH_INDICATORS = ["á", "é", "í", "ó", "ú", "ñ", "¿", "¡"]
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class TestPyprojectVersion(unittest.TestCase):
|
|
26
|
-
def
|
|
27
|
-
"""A minor bump:
|
|
28
|
-
from the public surface. Pinned here so
|
|
29
|
-
CHANGELOG cannot drift from what actually ships
|
|
30
|
-
what this caught: the first attempt at this release tagged v2.4.0
|
|
31
|
-
over a tree still pinned to 2.5.0, and the publish workflow's test
|
|
32
|
-
job would have refused to ship it."""
|
|
26
|
+
def test_version_is_2_5_0(self):
|
|
27
|
+
"""A minor bump: Cursor adapter and unified Gemini config path.
|
|
28
|
+
Additive, nothing removed from the public surface. Pinned here so
|
|
29
|
+
the release tag and the CHANGELOG cannot drift from what actually ships."""
|
|
33
30
|
with open(PYPROJECT_PATH, "r", encoding="utf-8") as f:
|
|
34
31
|
text = f.read()
|
|
35
32
|
match = re.search(r'^version\s*=\s*"([^"]+)"', text, re.MULTILINE)
|
|
36
33
|
self.assertIsNotNone(match, "version not declared")
|
|
37
|
-
self.assertEqual(match.group(1), "2.
|
|
34
|
+
self.assertEqual(match.group(1), "2.5.0")
|
|
38
35
|
|
|
39
36
|
def test_description_is_the_one_sentence_pitch(self):
|
|
40
37
|
"""PLAN.md 0.7's pitch doubles as the PyPI project description — the
|
|
@@ -543,7 +543,7 @@ class SkillCase(SetupCase):
|
|
|
543
543
|
"""
|
|
544
544
|
|
|
545
545
|
def skill_path(self):
|
|
546
|
-
return self.home_path(".gemini", "
|
|
546
|
+
return self.home_path(".gemini", "config", "skills", "context-guard", "SKILL.md")
|
|
547
547
|
|
|
548
548
|
def skill_text(self):
|
|
549
549
|
with open(self.skill_path(), "r", encoding="utf-8") as f:
|
|
@@ -594,7 +594,7 @@ class TestAntigravityGetsADiscoverableEntryPoint(SkillCase):
|
|
|
594
594
|
|
|
595
595
|
def test_the_skill_is_listed_as_touched(self):
|
|
596
596
|
res = self.setup(host="antigravity")
|
|
597
|
-
self.assertIn(".gemini/
|
|
597
|
+
self.assertIn(".gemini/config/skills/context-guard/SKILL.md", res.message)
|
|
598
598
|
|
|
599
599
|
def test_a_second_run_rewrites_it_byte_identically(self):
|
|
600
600
|
self.setup(host="antigravity")
|
|
@@ -742,7 +742,7 @@ class TestEveryHostGetsADiscoveryEntryPoint(SetupCase):
|
|
|
742
742
|
GLOBAL_DISCOVERY = {
|
|
743
743
|
"claude": ".claude/commands/cg-new.md",
|
|
744
744
|
"opencode": ".config/opencode/commands/cg-new.md",
|
|
745
|
-
"antigravity": ".gemini/
|
|
745
|
+
"antigravity": ".gemini/config/skills/context-guard/SKILL.md",
|
|
746
746
|
}
|
|
747
747
|
|
|
748
748
|
PROJECT_DISCOVERY = {
|
|
@@ -1055,3 +1055,61 @@ class TestEmbeddedCommandsAreScopeAgnostic(unittest.TestCase):
|
|
|
1055
1055
|
|
|
1056
1056
|
if __name__ == "__main__":
|
|
1057
1057
|
unittest.main()
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
|
|
1061
|
+
class TestCursorSetup(SetupCase):
|
|
1062
|
+
def rule_path(self, global_scope=True):
|
|
1063
|
+
if global_scope:
|
|
1064
|
+
return self.home_path(".cursor", "rules", "context-guard.mdc")
|
|
1065
|
+
return self.project_path(".cursor", "rules", "context-guard.mdc")
|
|
1066
|
+
|
|
1067
|
+
def mcp_path(self, global_scope=True):
|
|
1068
|
+
if global_scope:
|
|
1069
|
+
return self.home_path(".cursor", "mcp.json")
|
|
1070
|
+
return self.project_path(".cursor", "mcp.json")
|
|
1071
|
+
|
|
1072
|
+
def test_cursor_global_setup_installs_rule(self):
|
|
1073
|
+
res = self.setup(host="cursor")
|
|
1074
|
+
self.assertEqual(res.exit_code, EXIT_OK, res.message)
|
|
1075
|
+
self.assertTrue(os.path.exists(self.rule_path(global_scope=True)))
|
|
1076
|
+
with open(self.rule_path(global_scope=True), "r", encoding="utf-8") as f:
|
|
1077
|
+
content = f.read()
|
|
1078
|
+
self.assertIn("ACTIVE PERSISTENCE CONTRACT: context-guard", content)
|
|
1079
|
+
|
|
1080
|
+
def test_cursor_project_setup_installs_rule(self):
|
|
1081
|
+
res = self.setup(host="cursor", project=self.project)
|
|
1082
|
+
self.assertEqual(res.exit_code, EXIT_OK, res.message)
|
|
1083
|
+
self.assertTrue(os.path.exists(self.rule_path(global_scope=False)))
|
|
1084
|
+
|
|
1085
|
+
def test_cursor_with_mcp_registers_server(self):
|
|
1086
|
+
res = self.setup(host="cursor", with_mcp=True)
|
|
1087
|
+
self.assertEqual(res.exit_code, EXIT_OK, res.message)
|
|
1088
|
+
self.assertTrue(os.path.exists(self.mcp_path(global_scope=True)))
|
|
1089
|
+
cfg = self.read_json(self.mcp_path(global_scope=True))
|
|
1090
|
+
self.assertEqual(cfg["mcpServers"]["context-guard"]["command"], "context-guard-mcp")
|
|
1091
|
+
|
|
1092
|
+
def test_cursor_detection_via_dir(self):
|
|
1093
|
+
os.makedirs(self.home_path(".cursor"), exist_ok=True)
|
|
1094
|
+
self.setup(host="all")
|
|
1095
|
+
self.assertTrue(os.path.exists(self.rule_path(global_scope=True)))
|
|
1096
|
+
|
|
1097
|
+
def test_cursor_detection_via_binary(self):
|
|
1098
|
+
self.fake_binary("cursor")
|
|
1099
|
+
self.setup(host="all")
|
|
1100
|
+
self.assertTrue(os.path.exists(self.rule_path(global_scope=True)))
|
|
1101
|
+
|
|
1102
|
+
def test_cursor_not_detected_is_skipped(self):
|
|
1103
|
+
res = self.setup(host="all")
|
|
1104
|
+
self.assertFalse(os.path.exists(self.rule_path(global_scope=True)))
|
|
1105
|
+
self.assertIn("cursor: not detected, skipped", res.message)
|
|
1106
|
+
|
|
1107
|
+
def test_foreign_cursor_rule_not_clobbered(self):
|
|
1108
|
+
mine = "---\ndescription: custom\n---\n# custom rule\n"
|
|
1109
|
+
os.makedirs(os.path.dirname(self.rule_path()), exist_ok=True)
|
|
1110
|
+
with open(self.rule_path(), "w", encoding="utf-8") as f:
|
|
1111
|
+
f.write(mine)
|
|
1112
|
+
res = self.setup(host="cursor")
|
|
1113
|
+
with open(self.rule_path(), "r", encoding="utf-8") as f:
|
|
1114
|
+
self.assertEqual(f.read(), mine)
|
|
1115
|
+
self.assertIn("SKIP|SKILL_EXISTS", res.message)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/docs/adapters/antigravity/PERMISSIONS.md
RENAMED
|
File without changes
|
{context_guard_cli-2.4.0 → context_guard_cli-2.5.0}/docs/adapters/claude-code/PERMISSIONS.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|