context-guard-cli 2.2.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.
Files changed (87) hide show
  1. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/.github/workflows/ci.yml +6 -2
  2. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/.gitignore +2 -0
  3. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/CHANGELOG.md +56 -0
  4. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/PKG-INFO +43 -5
  5. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/README.es.md +42 -3
  6. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/README.md +41 -3
  7. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/TUTORIAL.es.md +2 -2
  8. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/antigravity/skills/context-guard/SKILL.md +6 -5
  9. context_guard_cli-2.5.0/context_guard/_data/hosts/cursor/mcp.snippet.json +7 -0
  10. context_guard_cli-2.5.0/context_guard/_data/hosts/cursor/rules/context-guard.mdc +20 -0
  11. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/phases/plan.md +7 -2
  12. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/assets.py +3 -3
  13. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/cli.py +17 -3
  14. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/commands.py +77 -0
  15. context_guard_cli-2.5.0/context_guard/guard/plan_import.py +253 -0
  16. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/setup.py +78 -6
  17. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/docs/adapters/VERIFY.md +11 -1
  18. context_guard_cli-2.5.0/docs/adapters/cursor/PERMISSIONS.md +42 -0
  19. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/pyproject.toml +1 -1
  20. context_guard_cli-2.5.0/tests/fixtures/plan_en.md +50 -0
  21. context_guard_cli-2.5.0/tests/fixtures/plan_es.md +60 -0
  22. context_guard_cli-2.5.0/tests/fixtures/plan_no_phases.md +12 -0
  23. context_guard_cli-2.5.0/tests/fixtures/plan_sentinel.md +19 -0
  24. context_guard_cli-2.5.0/tests/fixtures/plan_sparse.md +16 -0
  25. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_adapters.py +23 -2
  26. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_assets.py +14 -1
  27. context_guard_cli-2.5.0/tests/test_from_plan.py +262 -0
  28. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_packaging.py +6 -5
  29. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_phase_docs.py +41 -0
  30. context_guard_cli-2.5.0/tests/test_plan_import.py +202 -0
  31. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_release.py +5 -6
  32. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_setup.py +61 -3
  33. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/.agents/rules/context-guard.md +0 -0
  34. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/.githooks/pre-commit +0 -0
  35. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/.github/workflows/publish.yml +0 -0
  36. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/AGENTS.md +0 -0
  37. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/LICENSE +0 -0
  38. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/__init__.py +0 -0
  39. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/antigravity/hooks.snippet.json +0 -0
  40. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/antigravity/rules/context-guard.md +0 -0
  41. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/claude-code/commands/cg-continue.md +0 -0
  42. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/claude-code/commands/cg-new.md +0 -0
  43. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/claude-code/mcp.snippet.json +0 -0
  44. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/claude-code/settings.snippet.json +0 -0
  45. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/opencode/agent.snippet.json +0 -0
  46. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/opencode/commands/cg-continue.md +0 -0
  47. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/opencode/commands/cg-new.md +0 -0
  48. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/opencode/mcp.snippet.json +0 -0
  49. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/opencode/permissions.snippet.json +0 -0
  50. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/phases/execute.md +0 -0
  51. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/phases/verify.md +0 -0
  52. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/__init__.py +0 -0
  53. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/errors.py +0 -0
  54. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/locking.py +0 -0
  55. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/manifest.py +0 -0
  56. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/migrate.py +0 -0
  57. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/paths.py +0 -0
  58. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/transaction.py +0 -0
  59. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/mcp_server.py +0 -0
  60. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/docs/adapters/antigravity/PERMISSIONS.md +0 -0
  61. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/docs/adapters/claude-code/PERMISSIONS.md +0 -0
  62. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/docs/adapters/opencode/PERMISSIONS.md +0 -0
  63. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/__init__.py +0 -0
  64. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_adversarial.py +0 -0
  65. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_adversarial_approve.py +0 -0
  66. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_adversarial_migrate.py +0 -0
  67. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_adversarial_multichange.py +0 -0
  68. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_agents_md.py +0 -0
  69. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_archive.py +0 -0
  70. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_check_completion.py +0 -0
  71. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_commands.py +0 -0
  72. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_doctor.py +0 -0
  73. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_ergonomics.py +0 -0
  74. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_json_format.py +0 -0
  75. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_license.py +0 -0
  76. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_locking.py +0 -0
  77. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_manifest.py +0 -0
  78. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_mcp_server.py +0 -0
  79. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_next_task_status.py +0 -0
  80. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_phases.py +0 -0
  81. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_pre_commit_hook.py +0 -0
  82. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_publish.py +0 -0
  83. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_pyproject_pin.py +0 -0
  84. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_readme.py +0 -0
  85. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_readme_es.py +0 -0
  86. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_transaction.py +0 -0
  87. {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_validate.py +0 -0
@@ -1,10 +1,14 @@
1
1
  name: CI
2
2
 
3
+ # The push list is a pattern, not an enumeration: cycle branches follow vX.Y
4
+ # and the old explicit list went stale silently — v2.5 got no CI on push, only
5
+ # once its PR opened. On pull_request the filter matches the *base* branch, so
6
+ # main alone covers every PR into it.
3
7
  on:
4
8
  push:
5
- branches: [main, v2, v2.1, v2.2]
9
+ branches: [main, 'v[0-9]*']
6
10
  pull_request:
7
- branches: [main, v2, v2.1, v2.2]
11
+ branches: [main]
8
12
 
9
13
  jobs:
10
14
  test:
@@ -8,3 +8,5 @@ dist/
8
8
  PLAN.md
9
9
  PLAN-2.1.md
10
10
  RUNBOOK_HUMANO.md
11
+ .claude/*.local.json
12
+ uv.lock
@@ -6,6 +6,62 @@ 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
+
31
+ ## [2.4.0] - 2026-08-06
32
+
33
+ The bridge between a plan and a change was copy-paste. A phased `PLAN-N.md`
34
+ already holds what `cg new` leaves as `[PENDING]`; now the tool reads it.
35
+
36
+ ### Added
37
+
38
+ - `cg new <name> --from-plan <file>` imports a phased `PLAN-N.md`, creating
39
+ one change per `## F<N>` phase (`<name>-f1`, `<name>-f2`, …). Each phase's
40
+ prose and spec become `objective.md`; its test items and acceptance
41
+ criteria become `tasks.md` in the `- [ ] N.M <text>` form `next-task`
42
+ already parses. `--phase F2` imports one phase. `snapshot.md` stays
43
+ `[PENDING]` — it records the repository state at start, which no plan
44
+ written beforehand can know.
45
+ - `context_guard/guard/plan_import.py`: `parse_plan()` reads a plan into
46
+ title, one-sentence objective, and phases. Headings delimit phases;
47
+ `**Spec:**`, `**Tests:**` and `**Acceptance criteria:**` sub-blocks are
48
+ recognized in English and Spanish. A missing sub-block is empty, not an
49
+ error; a file with no phase heading is `FAIL|PLAN_NO_PHASES|<path>`.
50
+ Parsed with `re` alone — no new dependencies.
51
+ - Changes imported from a plan carry no approval. `cg commit --next-phase
52
+ EXECUTE` still exits 6 until a human runs `cg approve`, once per phase.
53
+ Covered by an adversarial test: a pre-written objective is not a reviewed
54
+ one.
55
+ - Re-importing skips changes that already exist
56
+ (`SKIP|CHANGE_EXISTS|<name>`) instead of overwriting work in flight.
57
+ - A plan quoting the scaffold sentinel `[PENDING]` in its own prose — any
58
+ plan about context-guard itself — would have produced a change the
59
+ PLAN→EXECUTE gate refuses as unfilled, stuck before the approval gate was
60
+ ever reached. The brackets are stripped on import and the substitution is
61
+ reported (`NOTE|SENTINEL_NEUTRALIZED|<change>|<file>`). Fixed on the
62
+ import side deliberately: the artifact really was filled, so loosening the
63
+ gate would have loosened it for every change, imported or not.
64
+
9
65
  ## [2.2.0] - 2026-08-02
10
66
 
11
67
  Triggered by real dogfooding: Antigravity never raised the context-guard
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: context-guard-cli
3
- Version: 2.2.0
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 Antigravity, installs the slash
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.
@@ -201,6 +201,7 @@ unconditional block just gets `--no-verify`d, which leaves no trace at all.
201
201
  | Command | Purpose |
202
202
  |---|---|
203
203
  | `cg new <name> --context <path>` | Create a change and begin PLAN |
204
+ | `cg new <name> --from-plan <file> [--phase F2]` | Import a phased `PLAN-N.md` as one change per phase |
204
205
  | `cg list --context <path>` | List active changes and their phase |
205
206
  | `cg begin --phase <PHASE> --context <path>` | Start a transaction for the given phase |
206
207
  | `cg approve [--by <who>] [--hotfix --reason "<text>"]` | Human-only: record the sign-off `commit` into EXECUTE requires |
@@ -243,6 +244,36 @@ shows what is active; `cg archive` moves a finished one to
243
244
  `state.ini` and context-guard 1.x's flat `.context-guard/` — in place and
244
245
  idempotently, preserving any recorded human approval it finds.
245
246
 
247
+ ## From a phased plan
248
+
249
+ If a cycle was already planned as a phased `PLAN-N.md` — the artifact
250
+ [disciplined-scaffold](https://github.com/fdomerlo/disciplined-scaffold)
251
+ produces after you talk a project through with an assistant — `cg new
252
+ --from-plan` materialises it instead of you retyping it:
253
+
254
+ ```bash
255
+ cg new redis --from-plan PLAN-3.md
256
+ ```
257
+
258
+ One change per `## F<N>` phase, named `redis-f1`, `redis-f2`, … Each phase's
259
+ prose and spec become its `objective.md`; its test items and acceptance
260
+ criteria become `tasks.md` as `- [ ] N.M <text>`, the shape `cg next-task`
261
+ already claims. `--phase F2` imports a single phase. Re-running skips changes
262
+ that already exist (`SKIP|CHANGE_EXISTS|<name>`) rather than overwriting work
263
+ in flight.
264
+
265
+ `snapshot.md` stays `[PENDING]`: it records the state of the repository when
266
+ work starts, which no plan written beforehand can know.
267
+
268
+ **Importing does not approve anything.** Each change lands in PLAN with no
269
+ approval in its manifest, so `cg commit --next-phase EXECUTE` still exits 6
270
+ until a human runs `cg approve` — once per phase. A pre-written objective is
271
+ still an unreviewed one.
272
+
273
+ The full flow: talk the project through → `disciplined-scaffold` writes
274
+ `PLAN-N.md` → `cg new --from-plan` → each phase executes under the same
275
+ approval gate as any other change.
276
+
246
277
  ## Pre-commit hook
247
278
 
248
279
  `.githooks/pre-commit` rejects commits touching more than a threshold number
@@ -276,7 +307,7 @@ an open transaction). Activate it once per clone with
276
307
  ## Adapters and permission configuration
277
308
 
278
309
  Thin, per-harness wrappers ship inside the package under
279
- `context_guard/_data/hosts/{claude-code,opencode,antigravity}/` — each points
310
+ `context_guard/_data/hosts/{claude-code,opencode,antigravity,cursor}/` — each points
280
311
  at `phases/{plan,execute,verify}.md` rather than duplicating them. How to put
281
312
  `cg approve` behind each harness's permission prompt is documented in
282
313
  [docs/adapters/](docs/adapters/), one `PERMISSIONS.md` per host, alongside the
@@ -302,6 +333,11 @@ turning a prompt into a good spec in the first place. Use context-guard
302
333
  together with whichever of them already generates your `objective.md` — it
303
334
  was designed to consume one, not to write one.
304
335
 
336
+ [disciplined-scaffold](https://github.com/fdomerlo/disciplined-scaffold) is
337
+ the lighter step before this one: markdown-only phase discipline, no
338
+ transactional state. When a project outgrows it, `cg new --from-plan` reads
339
+ the `PLAN-N.md` it produced — see [From a phased plan](#from-a-phased-plan).
340
+
305
341
  ## Development
306
342
 
307
343
  ```bash
@@ -312,7 +348,9 @@ python -m unittest discover -s tests
312
348
 
313
349
  Framework: `unittest`. Every fix ships with an adversarial test that
314
350
  reproduces the bypass it closes; see `tests/test_adversarial_*.py` for the
315
- pattern. No fixtures on disk outside `tempfile.mkdtemp()`.
351
+ pattern. Tests write only under `tempfile.mkdtemp()`; the sole read-only
352
+ fixtures on disk are the sample plans in `tests/fixtures/`, which exist
353
+ because `--from-plan` parses documents rather than generating them.
316
354
 
317
355
  ## License
318
356
 
@@ -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 Antigravity, instala los slash
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.
@@ -190,6 +190,7 @@ garantía de corrección, y viene con un bypass auditado
190
190
  | Comando | Propósito |
191
191
  |---|---|
192
192
  | `cg new <nombre> --context <ruta>` | Crea un change y arranca PLAN |
193
+ | `cg new <nombre> --from-plan <archivo> [--phase F2]` | Importa un `PLAN-N.md` por fases como un change por fase |
193
194
  | `cg list --context <ruta>` | Lista los changes activos y su fase |
194
195
  | `cg begin --phase <FASE> --context <ruta>` | Inicia una transacción para la fase dada |
195
196
  | `cg approve [--by <quién>] [--hotfix --reason "<texto>"]` | Solo humano: registra el visto bueno que `commit` exige para entrar a EXECUTE |
@@ -233,6 +234,36 @@ de forma independiente en el mismo proyecto. `cg new <nombre>` crea uno;
233
234
  1.x — in situ y de forma idempotente, preservando cualquier aprobación
234
235
  humana registrada que encuentre.
235
236
 
237
+ ## Desde un plan por fases
238
+
239
+ Si el ciclo ya se planificó como un `PLAN-N.md` por fases — el artefacto que
240
+ produce [disciplined-scaffold](https://github.com/fdomerlo/disciplined-scaffold)
241
+ después de debatir un proyecto con el asistente — `cg new --from-plan` lo
242
+ materializa en vez de que lo retipees:
243
+
244
+ ```bash
245
+ cg new redis --from-plan PLAN-3.md
246
+ ```
247
+
248
+ Un change por cada fase `## F<N>`, nombrados `redis-f1`, `redis-f2`, … La
249
+ prosa y el spec de cada fase se vuelven su `objective.md`; sus ítems de test
250
+ y criterios de aceptación se vuelven `tasks.md` en formato `- [ ] N.M
251
+ <texto>`, el que `cg next-task` ya reclama. `--phase F2` importa una sola
252
+ fase. Re-ejecutar saltea los changes que ya existen
253
+ (`SKIP|CHANGE_EXISTS|<nombre>`) en vez de pisar trabajo en curso.
254
+
255
+ `snapshot.md` queda `[PENDING]`: registra el estado del repositorio al
256
+ arrancar, que ningún plan escrito de antemano puede conocer.
257
+
258
+ **Importar no aprueba nada.** Cada change entra en PLAN sin aprobación en su
259
+ manifest, así que `cg commit --next-phase EXECUTE` sigue saliendo con código
260
+ 6 hasta que un humano corra `cg approve` — una vez por fase. Un objetivo
261
+ pre-escrito sigue siendo un objetivo sin revisar.
262
+
263
+ El flujo completo: debatir el proyecto → `disciplined-scaffold` escribe
264
+ `PLAN-N.md` → `cg new --from-plan` → cada fase se ejecuta bajo el mismo gate
265
+ de aprobación que cualquier otro change.
266
+
236
267
  ## Hook de pre-commit
237
268
 
238
269
  `.githooks/pre-commit` rechaza commits que tocan más de un umbral de
@@ -266,7 +297,7 @@ fase completada o una transacción abierta). Activalo una vez por clon con
266
297
  ## Adapters y configuración de permisos
267
298
 
268
299
  Dentro del paquete, en `context_guard/_data/hosts/{claude-code,opencode,
269
- antigravity}/`, viven wrappers finos por harness — cada uno apunta a
300
+ antigravity,cursor}/`, viven wrappers finos por harness — cada uno apunta a
270
301
  `phases/{plan,execute,verify}.md` en vez de duplicarlos. Cómo poner
271
302
  `cg approve` detrás del permission prompt de cada harness está documentado en
272
303
  [docs/adapters/](docs/adapters/), un `PERMISSIONS.md` por host, junto con el
@@ -296,6 +327,11 @@ lo referido a convertir un prompt en un buen spec en primer lugar. Usá
296
327
  context-guard junto con cualquiera de ellos que ya te genere el
297
328
  `objective.md` — está diseñado para consumir uno, no para escribirlo.
298
329
 
330
+ [disciplined-scaffold](https://github.com/fdomerlo/disciplined-scaffold) es
331
+ el paso liviano previo a este: disciplina de fases en markdown, sin estado
332
+ transaccional. Cuando un proyecto lo supera, `cg new --from-plan` lee el
333
+ `PLAN-N.md` que produjo — ver [Desde un plan por fases](#desde-un-plan-por-fases).
334
+
299
335
  ## Desarrollo
300
336
 
301
337
  ```bash
@@ -306,7 +342,10 @@ python -m unittest discover -s tests
306
342
 
307
343
  Framework: `unittest`. Cada fix se entrega con un test adversarial que
308
344
  reproduce el bypass que cierra; ver `tests/test_adversarial_*.py` para el
309
- patrón. Sin fixtures en disco fuera de `tempfile.mkdtemp()`.
345
+ patrón. Los tests solo escriben bajo `tempfile.mkdtemp()`; los únicos
346
+ fixtures en disco son los planes de ejemplo de solo lectura en
347
+ `tests/fixtures/`, que existen porque `--from-plan` parsea documentos en vez
348
+ de generarlos.
310
349
 
311
350
  ## Licencia
312
351
 
@@ -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 Antigravity, installs the slash
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.
@@ -188,6 +188,7 @@ unconditional block just gets `--no-verify`d, which leaves no trace at all.
188
188
  | Command | Purpose |
189
189
  |---|---|
190
190
  | `cg new <name> --context <path>` | Create a change and begin PLAN |
191
+ | `cg new <name> --from-plan <file> [--phase F2]` | Import a phased `PLAN-N.md` as one change per phase |
191
192
  | `cg list --context <path>` | List active changes and their phase |
192
193
  | `cg begin --phase <PHASE> --context <path>` | Start a transaction for the given phase |
193
194
  | `cg approve [--by <who>] [--hotfix --reason "<text>"]` | Human-only: record the sign-off `commit` into EXECUTE requires |
@@ -230,6 +231,36 @@ shows what is active; `cg archive` moves a finished one to
230
231
  `state.ini` and context-guard 1.x's flat `.context-guard/` — in place and
231
232
  idempotently, preserving any recorded human approval it finds.
232
233
 
234
+ ## From a phased plan
235
+
236
+ If a cycle was already planned as a phased `PLAN-N.md` — the artifact
237
+ [disciplined-scaffold](https://github.com/fdomerlo/disciplined-scaffold)
238
+ produces after you talk a project through with an assistant — `cg new
239
+ --from-plan` materialises it instead of you retyping it:
240
+
241
+ ```bash
242
+ cg new redis --from-plan PLAN-3.md
243
+ ```
244
+
245
+ One change per `## F<N>` phase, named `redis-f1`, `redis-f2`, … Each phase's
246
+ prose and spec become its `objective.md`; its test items and acceptance
247
+ criteria become `tasks.md` as `- [ ] N.M <text>`, the shape `cg next-task`
248
+ already claims. `--phase F2` imports a single phase. Re-running skips changes
249
+ that already exist (`SKIP|CHANGE_EXISTS|<name>`) rather than overwriting work
250
+ in flight.
251
+
252
+ `snapshot.md` stays `[PENDING]`: it records the state of the repository when
253
+ work starts, which no plan written beforehand can know.
254
+
255
+ **Importing does not approve anything.** Each change lands in PLAN with no
256
+ approval in its manifest, so `cg commit --next-phase EXECUTE` still exits 6
257
+ until a human runs `cg approve` — once per phase. A pre-written objective is
258
+ still an unreviewed one.
259
+
260
+ The full flow: talk the project through → `disciplined-scaffold` writes
261
+ `PLAN-N.md` → `cg new --from-plan` → each phase executes under the same
262
+ approval gate as any other change.
263
+
233
264
  ## Pre-commit hook
234
265
 
235
266
  `.githooks/pre-commit` rejects commits touching more than a threshold number
@@ -263,7 +294,7 @@ an open transaction). Activate it once per clone with
263
294
  ## Adapters and permission configuration
264
295
 
265
296
  Thin, per-harness wrappers ship inside the package under
266
- `context_guard/_data/hosts/{claude-code,opencode,antigravity}/` — each points
297
+ `context_guard/_data/hosts/{claude-code,opencode,antigravity,cursor}/` — each points
267
298
  at `phases/{plan,execute,verify}.md` rather than duplicating them. How to put
268
299
  `cg approve` behind each harness's permission prompt is documented in
269
300
  [docs/adapters/](docs/adapters/), one `PERMISSIONS.md` per host, alongside the
@@ -289,6 +320,11 @@ turning a prompt into a good spec in the first place. Use context-guard
289
320
  together with whichever of them already generates your `objective.md` — it
290
321
  was designed to consume one, not to write one.
291
322
 
323
+ [disciplined-scaffold](https://github.com/fdomerlo/disciplined-scaffold) is
324
+ the lighter step before this one: markdown-only phase discipline, no
325
+ transactional state. When a project outgrows it, `cg new --from-plan` reads
326
+ the `PLAN-N.md` it produced — see [From a phased plan](#from-a-phased-plan).
327
+
292
328
  ## Development
293
329
 
294
330
  ```bash
@@ -299,7 +335,9 @@ python -m unittest discover -s tests
299
335
 
300
336
  Framework: `unittest`. Every fix ships with an adversarial test that
301
337
  reproduces the bypass it closes; see `tests/test_adversarial_*.py` for the
302
- pattern. No fixtures on disk outside `tempfile.mkdtemp()`.
338
+ pattern. Tests write only under `tempfile.mkdtemp()`; the sole read-only
339
+ fixtures on disk are the sample plans in `tests/fixtures/`, which exist
340
+ because `--from-plan` parses documents rather than generating them.
303
341
 
304
342
  ## License
305
343
 
@@ -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 Antigravity. Cualquiera de los tres funciona; los comandos del asistente son los mismos.
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
  ---
@@ -1,11 +1,12 @@
1
1
  ---
2
2
  name: context-guard
3
3
  description: >-
4
- Use when the user asks for a multi-step coding task, when they ask to
5
- resume work after a lost, crashed or compacted session, or in any project
6
- that already contains a `.context-guard/` directory. Drives the `cg` CLI so
7
- a phase-governed change keeps its plan, its progress and its verification
8
- on disk instead of in the conversation.
4
+ Use when the user asks to build, implement, or scaffold an application or
5
+ feature; for any multi-step coding task; to resume work after a lost,
6
+ crashed or compacted session; or in any project that already contains a
7
+ `.context-guard/` directory. Drives the `cg` CLI so a phase-governed change
8
+ keeps its plan, its progress and its verification on disk instead of in
9
+ the conversation.
9
10
  ---
10
11
 
11
12
  <!-- context-guard:begin -->
@@ -0,0 +1,7 @@
1
+ {
2
+ "mcpServers": {
3
+ "context-guard": {
4
+ "command": "context-guard-mcp"
5
+ }
6
+ }
7
+ }
@@ -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 -->
@@ -122,8 +122,13 @@ cg commit --context <path> --change <change-name> --next-phase EXECUTE
122
122
  ```
123
123
 
124
124
  This advances `lock_phase` to `EXECUTE`, releases the phase lock, and
125
- auto-generates a checkpoint of the DAG state. Report to the user that PLAN is
126
- locked and EXECUTE is open, with the suggested next command: `/cg-continue`.
125
+ auto-generates a checkpoint of the DAG state.
126
+
127
+ Report to the user that PLAN is locked and EXECUTE is open. Then, in this
128
+ same turn, load `.context-guard/phases/execute.md` and continue directly —
129
+ do not wait for `/cg-continue`. Only stop and suggest `/cg-continue` if you
130
+ are ending the turn for an unrelated reason, or the session might not
131
+ continue (e.g., you are about to hit a context limit).
127
132
 
128
133
  ## Rules
129
134
 
@@ -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
 
@@ -12,6 +12,7 @@ from .commands import (
12
12
  cmd_approve,
13
13
  cmd_check_lock,
14
14
  cmd_new,
15
+ cmd_new_from_plan,
15
16
  cmd_setup,
16
17
  cmd_list,
17
18
  cmd_migrate,
@@ -130,16 +131,24 @@ def parse_args(argv=None):
130
131
  p_new = subparsers.add_parser("new")
131
132
  p_new.add_argument("--context", default=".")
132
133
  p_new.add_argument("name")
133
- p_new.add_argument("--host", choices=["claude", "opencode", "antigravity"],
134
+ p_new.add_argument("--host", choices=["claude", "opencode", "antigravity", "cursor"],
134
135
  default=None,
135
136
  help="Also materialise this host's workspace files "
136
137
  "(Antigravity's rule file). Detected automatically "
137
138
  "when omitted.")
139
+ p_new.add_argument("--from-plan", dest="from_plan", default=None,
140
+ help="Import a phased PLAN-N.md, creating one change "
141
+ "per phase (<name>-f1, <name>-f2, ...) with "
142
+ "objective.md and tasks.md derived from it. The "
143
+ "approval gate is unchanged: each change still "
144
+ "needs cg approve to reach EXECUTE.")
145
+ p_new.add_argument("--phase", default=None,
146
+ help="With --from-plan, import only this phase (e.g. F2).")
138
147
 
139
148
  # setup takes no --context: it configures hosts, not a change. --project
140
149
  # opts back into 2.0's per-project install for teams committing the config.
141
150
  p_setup = subparsers.add_parser("setup")
142
- p_setup.add_argument("--host", choices=["claude", "opencode", "antigravity", "all"],
151
+ p_setup.add_argument("--host", choices=["claude", "opencode", "antigravity", "cursor", "all"],
143
152
  default="all")
144
153
  p_setup.add_argument("--with-mcp", action="store_true",
145
154
  help="Also register the context-guard-mcp server. "
@@ -180,7 +189,12 @@ def dispatch(args):
180
189
  """
181
190
  change = getattr(args, "change", None)
182
191
  handlers = {
183
- "new": lambda: cmd_new(args.context, args.name, args.host),
192
+ "new": lambda: (
193
+ cmd_new_from_plan(args.context, args.name, args.from_plan,
194
+ phase=args.phase, host=args.host)
195
+ if args.from_plan
196
+ else cmd_new(args.context, args.name, args.host)
197
+ ),
184
198
  "setup": lambda: cmd_setup(
185
199
  host=args.host, with_mcp=args.with_mcp, project=args.project,
186
200
  no_hooks=args.no_hooks),
@@ -29,10 +29,18 @@ from .transaction import (
29
29
  cmd_checkpoint,
30
30
  )
31
31
  from .migrate import cmd_migrate
32
+ from .plan_import import (
33
+ neutralize_sentinel,
34
+ parse_plan,
35
+ phase_objective,
36
+ phase_tasks,
37
+ )
32
38
  from .setup import (
33
39
  antigravity_detected,
40
+ cursor_detected,
34
41
  diverged_phases,
35
42
  materialise_antigravity_rule,
43
+ materialise_cursor_rule,
36
44
  materialise_phases,
37
45
  run_setup,
38
46
  )
@@ -137,10 +145,79 @@ def cmd_new(context, change, host=None):
137
145
  materialise_phases(context)
138
146
  if host == "antigravity" or (host is None and antigravity_detected()):
139
147
  materialise_antigravity_rule(context)
148
+ if host == "cursor" or (host is None and cursor_detected()):
149
+ materialise_cursor_rule(context)
140
150
 
141
151
  return CommandResult(f"SUCCESS|CHANGE_CREATED|{name}|phase=PLAN", EXIT_OK)
142
152
 
143
153
 
154
+ def cmd_new_from_plan(context, change, plan_path, phase=None, host=None):
155
+ """Materialise a phased PLAN-N.md as one change per phase.
156
+
157
+ Each change is created through cmd_new — the scaffolding logic is not
158
+ forked — and only then are objective.md and tasks.md overwritten with the
159
+ content derived from the plan. snapshot.md is deliberately left [PENDING]:
160
+ it records the state of the repository at the moment work starts, which no
161
+ plan can know in advance.
162
+
163
+ A pre-written objective is not an approved one. The change lands in PLAN
164
+ with no `approval` in its manifest, so the commit into EXECUTE still fails
165
+ with APPROVAL_REQUIRED until a human runs `cg approve`.
166
+ """
167
+ # Parsed before anything is created: a plan with no phases must leave no
168
+ # half-imported changes behind.
169
+ plan = parse_plan(plan_path)
170
+
171
+ selected = plan.phases
172
+ if phase is not None:
173
+ wanted = phase.strip().upper()
174
+ selected = [p for p in plan.phases if p.id == wanted]
175
+ if not selected:
176
+ available = ",".join(p.id for p in plan.phases)
177
+ return CommandResult(
178
+ f"FAIL|PHASE_NOT_IN_PLAN|{phase}|available={available}",
179
+ EXIT_VALIDATION,
180
+ )
181
+
182
+ lines = []
183
+ created = 0
184
+ for p in selected:
185
+ change_name = f"{change}-{p.id.lower()}"
186
+ result = cmd_new(context, change_name, host=host)
187
+
188
+ if result.exit_code != EXIT_OK:
189
+ if "CHANGE_EXISTS" in result.message:
190
+ # Never overwrite a change already in flight.
191
+ lines.append(f"SKIP|CHANGE_EXISTS|{change_name}")
192
+ continue
193
+ return result
194
+
195
+ paths = get_paths(context, change_name)
196
+ created += 1
197
+ lines.append(f"SUCCESS|CHANGE_CREATED|{change_name}|phase=PLAN")
198
+
199
+ derived = {
200
+ "objective.md": phase_objective(plan, p),
201
+ "tasks.md": phase_tasks(plan, p),
202
+ }
203
+ for filename, content in derived.items():
204
+ # A plan quoting the scaffold sentinel would otherwise write a
205
+ # change the hard gate refuses as unfilled. Reported, never silent.
206
+ content, neutralized = neutralize_sentinel(content)
207
+ _write_artifact(paths["base"], filename, content)
208
+ if neutralized:
209
+ lines.append(
210
+ f"NOTE|SENTINEL_NEUTRALIZED|{change_name}|{filename}")
211
+
212
+ lines.append(f"IMPORTED|{created}|from={os.path.basename(plan_path)}")
213
+ return CommandResult("\n".join(lines), EXIT_OK)
214
+
215
+
216
+ def _write_artifact(base_dir, filename, content):
217
+ with open(os.path.join(base_dir, filename), "w", encoding="utf-8") as f:
218
+ f.write(content)
219
+
220
+
144
221
  def cmd_list(context):
145
222
  """Lista los changes activos con su fase actual.
146
223