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.
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/.github/workflows/ci.yml +6 -2
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/CHANGELOG.md +34 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/PKG-INFO +40 -2
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/README.es.md +40 -1
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/README.md +39 -1
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/antigravity/skills/context-guard/SKILL.md +6 -5
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/phases/plan.md +7 -2
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/cli.py +15 -1
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/commands.py +73 -0
- context_guard_cli-2.4.0/context_guard/guard/plan_import.py +253 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/docs/adapters/VERIFY.md +9 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/pyproject.toml +1 -1
- context_guard_cli-2.4.0/tests/fixtures/plan_en.md +50 -0
- context_guard_cli-2.4.0/tests/fixtures/plan_es.md +60 -0
- context_guard_cli-2.4.0/tests/fixtures/plan_no_phases.md +12 -0
- context_guard_cli-2.4.0/tests/fixtures/plan_sentinel.md +19 -0
- context_guard_cli-2.4.0/tests/fixtures/plan_sparse.md +16 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_assets.py +7 -1
- context_guard_cli-2.4.0/tests/test_from_plan.py +262 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_packaging.py +4 -3
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_phase_docs.py +41 -0
- context_guard_cli-2.4.0/tests/test_plan_import.py +202 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_release.py +8 -6
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/.agents/rules/context-guard.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/.githooks/pre-commit +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/.github/workflows/publish.yml +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/.gitignore +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/AGENTS.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/LICENSE +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/TUTORIAL.es.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/__init__.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/antigravity/hooks.snippet.json +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/antigravity/rules/context-guard.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/claude-code/commands/cg-continue.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/claude-code/commands/cg-new.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/claude-code/mcp.snippet.json +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/claude-code/settings.snippet.json +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/opencode/agent.snippet.json +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/opencode/commands/cg-continue.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/opencode/commands/cg-new.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/opencode/mcp.snippet.json +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/hosts/opencode/permissions.snippet.json +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/phases/execute.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/_data/phases/verify.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/__init__.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/assets.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/errors.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/locking.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/manifest.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/migrate.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/paths.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/setup.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/guard/transaction.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/context_guard/mcp_server.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/docs/adapters/antigravity/PERMISSIONS.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/docs/adapters/claude-code/PERMISSIONS.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/docs/adapters/opencode/PERMISSIONS.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/__init__.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_adapters.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_adversarial.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_adversarial_approve.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_adversarial_migrate.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_adversarial_multichange.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_agents_md.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_archive.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_check_completion.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_commands.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_doctor.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_ergonomics.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_json_format.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_license.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_locking.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_manifest.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_mcp_server.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_next_task_status.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_phases.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_pre_commit_hook.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_publish.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_pyproject_pin.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_readme.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_readme_es.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_setup.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.4.0}/tests/test_transaction.py +0 -0
- {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,
|
|
9
|
+
branches: [main, 'v[0-9]*']
|
|
6
10
|
pull_request:
|
|
7
|
-
branches: [main
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
on disk instead of in
|
|
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.
|
|
126
|
-
|
|
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:
|
|
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
|
|