context-guard-cli 2.2.0__tar.gz → 2.4.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 (84) hide show
  1. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/.github/workflows/ci.yml +6 -2
  2. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/CHANGELOG.md +34 -0
  3. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/PKG-INFO +40 -2
  4. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/README.es.md +40 -1
  5. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/README.md +39 -1
  6. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/antigravity/skills/context-guard/SKILL.md +6 -5
  7. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/phases/plan.md +7 -2
  8. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/cli.py +15 -1
  9. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/commands.py +73 -0
  10. context_guard_cli-2.4.0/context_guard/guard/plan_import.py +253 -0
  11. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/docs/adapters/VERIFY.md +9 -0
  12. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/pyproject.toml +1 -1
  13. context_guard_cli-2.4.0/tests/fixtures/plan_en.md +50 -0
  14. context_guard_cli-2.4.0/tests/fixtures/plan_es.md +60 -0
  15. context_guard_cli-2.4.0/tests/fixtures/plan_no_phases.md +12 -0
  16. context_guard_cli-2.4.0/tests/fixtures/plan_sentinel.md +19 -0
  17. context_guard_cli-2.4.0/tests/fixtures/plan_sparse.md +16 -0
  18. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_assets.py +7 -1
  19. context_guard_cli-2.4.0/tests/test_from_plan.py +262 -0
  20. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_packaging.py +4 -3
  21. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_phase_docs.py +41 -0
  22. context_guard_cli-2.4.0/tests/test_plan_import.py +202 -0
  23. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_release.py +8 -6
  24. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/.agents/rules/context-guard.md +0 -0
  25. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/.githooks/pre-commit +0 -0
  26. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/.github/workflows/publish.yml +0 -0
  27. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/.gitignore +0 -0
  28. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/AGENTS.md +0 -0
  29. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/LICENSE +0 -0
  30. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/TUTORIAL.es.md +0 -0
  31. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/__init__.py +0 -0
  32. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/antigravity/hooks.snippet.json +0 -0
  33. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/antigravity/rules/context-guard.md +0 -0
  34. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/claude-code/commands/cg-continue.md +0 -0
  35. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/claude-code/commands/cg-new.md +0 -0
  36. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/claude-code/mcp.snippet.json +0 -0
  37. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/claude-code/settings.snippet.json +0 -0
  38. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/opencode/agent.snippet.json +0 -0
  39. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/opencode/commands/cg-continue.md +0 -0
  40. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/opencode/commands/cg-new.md +0 -0
  41. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/opencode/mcp.snippet.json +0 -0
  42. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/opencode/permissions.snippet.json +0 -0
  43. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/phases/execute.md +0 -0
  44. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/phases/verify.md +0 -0
  45. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/__init__.py +0 -0
  46. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/assets.py +0 -0
  47. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/errors.py +0 -0
  48. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/locking.py +0 -0
  49. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/manifest.py +0 -0
  50. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/migrate.py +0 -0
  51. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/paths.py +0 -0
  52. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/setup.py +0 -0
  53. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/transaction.py +0 -0
  54. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/mcp_server.py +0 -0
  55. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/docs/adapters/antigravity/PERMISSIONS.md +0 -0
  56. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/docs/adapters/claude-code/PERMISSIONS.md +0 -0
  57. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/docs/adapters/opencode/PERMISSIONS.md +0 -0
  58. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/__init__.py +0 -0
  59. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_adapters.py +0 -0
  60. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_adversarial.py +0 -0
  61. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_adversarial_approve.py +0 -0
  62. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_adversarial_migrate.py +0 -0
  63. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_adversarial_multichange.py +0 -0
  64. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_agents_md.py +0 -0
  65. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_archive.py +0 -0
  66. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_check_completion.py +0 -0
  67. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_commands.py +0 -0
  68. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_doctor.py +0 -0
  69. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_ergonomics.py +0 -0
  70. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_json_format.py +0 -0
  71. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_license.py +0 -0
  72. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_locking.py +0 -0
  73. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_manifest.py +0 -0
  74. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_mcp_server.py +0 -0
  75. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_next_task_status.py +0 -0
  76. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_phases.py +0 -0
  77. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_pre_commit_hook.py +0 -0
  78. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_publish.py +0 -0
  79. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_pyproject_pin.py +0 -0
  80. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_readme.py +0 -0
  81. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_readme_es.py +0 -0
  82. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_setup.py +0 -0
  83. {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_transaction.py +0 -0
  84. {context_guard_cli-2.2.0 → context_guard_cli-2.4.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:
@@ -6,6 +6,40 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
 
7
7
  ---
8
8
 
9
+ ## [2.4.0] - 2026-08-06
10
+
11
+ The bridge between a plan and a change was copy-paste. A phased `PLAN-N.md`
12
+ already holds what `cg new` leaves as `[PENDING]`; now the tool reads it.
13
+
14
+ ### Added
15
+
16
+ - `cg new <name> --from-plan <file>` imports a phased `PLAN-N.md`, creating
17
+ one change per `## F<N>` phase (`<name>-f1`, `<name>-f2`, …). Each phase's
18
+ prose and spec become `objective.md`; its test items and acceptance
19
+ criteria become `tasks.md` in the `- [ ] N.M <text>` form `next-task`
20
+ already parses. `--phase F2` imports one phase. `snapshot.md` stays
21
+ `[PENDING]` — it records the repository state at start, which no plan
22
+ written beforehand can know.
23
+ - `context_guard/guard/plan_import.py`: `parse_plan()` reads a plan into
24
+ title, one-sentence objective, and phases. Headings delimit phases;
25
+ `**Spec:**`, `**Tests:**` and `**Acceptance criteria:**` sub-blocks are
26
+ recognized in English and Spanish. A missing sub-block is empty, not an
27
+ error; a file with no phase heading is `FAIL|PLAN_NO_PHASES|<path>`.
28
+ Parsed with `re` alone — no new dependencies.
29
+ - Changes imported from a plan carry no approval. `cg commit --next-phase
30
+ EXECUTE` still exits 6 until a human runs `cg approve`, once per phase.
31
+ Covered by an adversarial test: a pre-written objective is not a reviewed
32
+ one.
33
+ - Re-importing skips changes that already exist
34
+ (`SKIP|CHANGE_EXISTS|<name>`) instead of overwriting work in flight.
35
+ - A plan quoting the scaffold sentinel `[PENDING]` in its own prose — any
36
+ plan about context-guard itself — would have produced a change the
37
+ PLAN→EXECUTE gate refuses as unfilled, stuck before the approval gate was
38
+ ever reached. The brackets are stripped on import and the substitution is
39
+ reported (`NOTE|SENTINEL_NEUTRALIZED|<change>|<file>`). Fixed on the
40
+ import side deliberately: the artifact really was filled, so loosening the
41
+ gate would have loosened it for every change, imported or not.
42
+
9
43
  ## [2.2.0] - 2026-08-02
10
44
 
11
45
  Triggered by real dogfooding: Antigravity never raised the context-guard
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: context-guard-cli
3
- Version: 2.2.0
3
+ Version: 2.4.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
@@ -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
@@ -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
 
@@ -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
@@ -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
 
@@ -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
@@ -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,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 -->
@@ -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
 
@@ -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,
@@ -135,6 +136,14 @@ def parse_args(argv=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.
@@ -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,6 +29,12 @@ 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,
34
40
  diverged_phases,
@@ -141,6 +147,73 @@ def cmd_new(context, change, host=None):
141
147
  return CommandResult(f"SUCCESS|CHANGE_CREATED|{name}|phase=PLAN", EXIT_OK)
142
148
 
143
149
 
150
+ def cmd_new_from_plan(context, change, plan_path, phase=None, host=None):
151
+ """Materialise a phased PLAN-N.md as one change per phase.
152
+
153
+ Each change is created through cmd_new — the scaffolding logic is not
154
+ forked — and only then are objective.md and tasks.md overwritten with the
155
+ content derived from the plan. snapshot.md is deliberately left [PENDING]:
156
+ it records the state of the repository at the moment work starts, which no
157
+ plan can know in advance.
158
+
159
+ A pre-written objective is not an approved one. The change lands in PLAN
160
+ with no `approval` in its manifest, so the commit into EXECUTE still fails
161
+ with APPROVAL_REQUIRED until a human runs `cg approve`.
162
+ """
163
+ # Parsed before anything is created: a plan with no phases must leave no
164
+ # half-imported changes behind.
165
+ plan = parse_plan(plan_path)
166
+
167
+ selected = plan.phases
168
+ if phase is not None:
169
+ wanted = phase.strip().upper()
170
+ selected = [p for p in plan.phases if p.id == wanted]
171
+ if not selected:
172
+ available = ",".join(p.id for p in plan.phases)
173
+ return CommandResult(
174
+ f"FAIL|PHASE_NOT_IN_PLAN|{phase}|available={available}",
175
+ EXIT_VALIDATION,
176
+ )
177
+
178
+ lines = []
179
+ created = 0
180
+ for p in selected:
181
+ change_name = f"{change}-{p.id.lower()}"
182
+ result = cmd_new(context, change_name, host=host)
183
+
184
+ if result.exit_code != EXIT_OK:
185
+ if "CHANGE_EXISTS" in result.message:
186
+ # Never overwrite a change already in flight.
187
+ lines.append(f"SKIP|CHANGE_EXISTS|{change_name}")
188
+ continue
189
+ return result
190
+
191
+ paths = get_paths(context, change_name)
192
+ created += 1
193
+ lines.append(f"SUCCESS|CHANGE_CREATED|{change_name}|phase=PLAN")
194
+
195
+ derived = {
196
+ "objective.md": phase_objective(plan, p),
197
+ "tasks.md": phase_tasks(plan, p),
198
+ }
199
+ for filename, content in derived.items():
200
+ # A plan quoting the scaffold sentinel would otherwise write a
201
+ # change the hard gate refuses as unfilled. Reported, never silent.
202
+ content, neutralized = neutralize_sentinel(content)
203
+ _write_artifact(paths["base"], filename, content)
204
+ if neutralized:
205
+ lines.append(
206
+ f"NOTE|SENTINEL_NEUTRALIZED|{change_name}|{filename}")
207
+
208
+ lines.append(f"IMPORTED|{created}|from={os.path.basename(plan_path)}")
209
+ return CommandResult("\n".join(lines), EXIT_OK)
210
+
211
+
212
+ def _write_artifact(base_dir, filename, content):
213
+ with open(os.path.join(base_dir, filename), "w", encoding="utf-8") as f:
214
+ f.write(content)
215
+
216
+
144
217
  def cmd_list(context):
145
218
  """Lista los changes activos con su fase actual.
146
219