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.
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/.github/workflows/ci.yml +6 -2
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/.gitignore +2 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/CHANGELOG.md +56 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/PKG-INFO +43 -5
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/README.es.md +42 -3
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/README.md +41 -3
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/TUTORIAL.es.md +2 -2
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/antigravity/skills/context-guard/SKILL.md +6 -5
- context_guard_cli-2.5.0/context_guard/_data/hosts/cursor/mcp.snippet.json +7 -0
- context_guard_cli-2.5.0/context_guard/_data/hosts/cursor/rules/context-guard.mdc +20 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/phases/plan.md +7 -2
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/assets.py +3 -3
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/cli.py +17 -3
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/commands.py +77 -0
- context_guard_cli-2.5.0/context_guard/guard/plan_import.py +253 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/setup.py +78 -6
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/docs/adapters/VERIFY.md +11 -1
- context_guard_cli-2.5.0/docs/adapters/cursor/PERMISSIONS.md +42 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/pyproject.toml +1 -1
- context_guard_cli-2.5.0/tests/fixtures/plan_en.md +50 -0
- context_guard_cli-2.5.0/tests/fixtures/plan_es.md +60 -0
- context_guard_cli-2.5.0/tests/fixtures/plan_no_phases.md +12 -0
- context_guard_cli-2.5.0/tests/fixtures/plan_sentinel.md +19 -0
- context_guard_cli-2.5.0/tests/fixtures/plan_sparse.md +16 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_adapters.py +23 -2
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_assets.py +14 -1
- context_guard_cli-2.5.0/tests/test_from_plan.py +262 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_packaging.py +6 -5
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_phase_docs.py +41 -0
- context_guard_cli-2.5.0/tests/test_plan_import.py +202 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_release.py +5 -6
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_setup.py +61 -3
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/.agents/rules/context-guard.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/.githooks/pre-commit +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/.github/workflows/publish.yml +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/AGENTS.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/LICENSE +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/__init__.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/antigravity/hooks.snippet.json +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/antigravity/rules/context-guard.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/claude-code/commands/cg-continue.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/claude-code/commands/cg-new.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/claude-code/mcp.snippet.json +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/claude-code/settings.snippet.json +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/opencode/agent.snippet.json +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/opencode/commands/cg-continue.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/opencode/commands/cg-new.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/opencode/mcp.snippet.json +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/hosts/opencode/permissions.snippet.json +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/phases/execute.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/_data/phases/verify.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/__init__.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/errors.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/locking.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/manifest.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/migrate.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/paths.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/guard/transaction.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/context_guard/mcp_server.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/docs/adapters/antigravity/PERMISSIONS.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/docs/adapters/claude-code/PERMISSIONS.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/docs/adapters/opencode/PERMISSIONS.md +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/__init__.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_adversarial.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_adversarial_approve.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_adversarial_migrate.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_adversarial_multichange.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_agents_md.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_archive.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_check_completion.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_commands.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_doctor.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_ergonomics.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_json_format.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_license.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_locking.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_manifest.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_mcp_server.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_next_task_status.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_phases.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_pre_commit_hook.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_publish.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_pyproject_pin.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_readme.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_readme_es.py +0 -0
- {context_guard_cli-2.2.0 → context_guard_cli-2.5.0}/tests/test_transaction.py +0 -0
- {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,
|
|
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,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.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: context-guard-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: The transactional memory layer for AI coding agents — your context survives crashes, compaction, and session loss
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -95,7 +95,7 @@ context-guard-cli`**: that installs into whichever venv is currently active —
|
|
|
95
95
|
a single project's, if you happen to be inside one — not onto the machine,
|
|
96
96
|
which silently defeats the point of a global `cg setup`.
|
|
97
97
|
|
|
98
|
-
`cg setup` detects Claude Code, OpenCode and
|
|
98
|
+
`cg setup` detects Claude Code, OpenCode, Antigravity and Cursor, installs the slash
|
|
99
99
|
commands, and puts `cg approve` behind each one's permission prompt — see
|
|
100
100
|
[Adapters](#adapters-and-permission-configuration). It prints every file it
|
|
101
101
|
touched, and running it again changes nothing.
|
|
@@ -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.
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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 -->
|
|
@@ -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.
|
|
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
|
|
|
@@ -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:
|
|
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
|
|