stepgate 0.1.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.
- stepgate-0.1.0/.gitignore +12 -0
- stepgate-0.1.0/LICENSE +21 -0
- stepgate-0.1.0/PKG-INFO +162 -0
- stepgate-0.1.0/README.md +133 -0
- stepgate-0.1.0/pyproject.toml +51 -0
- stepgate-0.1.0/src/stepgate/__init__.py +3 -0
- stepgate-0.1.0/src/stepgate/__main__.py +4 -0
- stepgate-0.1.0/src/stepgate/cli.py +126 -0
- stepgate-0.1.0/src/stepgate/commands/__init__.py +0 -0
- stepgate-0.1.0/src/stepgate/commands/doctor.py +62 -0
- stepgate-0.1.0/src/stepgate/commands/init_cmd.py +132 -0
- stepgate-0.1.0/src/stepgate/commands/lifecycle.py +238 -0
- stepgate-0.1.0/src/stepgate/commands/views.py +129 -0
- stepgate-0.1.0/src/stepgate/model.py +188 -0
- stepgate-0.1.0/src/stepgate/render.py +83 -0
- stepgate-0.1.0/src/stepgate/store.py +166 -0
- stepgate-0.1.0/tests/conftest.py +34 -0
- stepgate-0.1.0/tests/test_cli_smoke.py +137 -0
- stepgate-0.1.0/tests/test_init.py +66 -0
- stepgate-0.1.0/tests/test_state_machine.py +78 -0
- stepgate-0.1.0/tests/test_store.py +70 -0
stepgate-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Leo Costta
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
stepgate-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: stepgate
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A step-gated micro-change protocol CLI for coding agents: propose, approve, execute, verify — one small step at a time.
|
|
5
|
+
Project-URL: Homepage, https://github.com/LeoCostta/stepgate
|
|
6
|
+
Project-URL: Repository, https://github.com/LeoCostta/stepgate
|
|
7
|
+
Project-URL: Issues, https://github.com/LeoCostta/stepgate/issues
|
|
8
|
+
Author: Leo Costta
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: claude-code,cli,codex,coding-agents,guardrails,workflow
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: filelock>=3.12
|
|
25
|
+
Requires-Dist: rich>=13.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# stepgate
|
|
31
|
+
|
|
32
|
+
[](https://pypi.org/project/stepgate/)
|
|
33
|
+
[](https://pypi.org/project/stepgate/)
|
|
34
|
+
[](LICENSE)
|
|
35
|
+
|
|
36
|
+
**A step-gated micro-change protocol for coding agents.**
|
|
37
|
+
|
|
38
|
+
Coding agents (Claude Code, Codex, …) working on large or loosely-scoped tasks
|
|
39
|
+
tend to mix contexts, silently expand scope, touch files unrelated to the
|
|
40
|
+
original request, or declare success without real evidence. `stepgate` makes
|
|
41
|
+
the correct workflow — investigate, propose, wait for approval, execute only
|
|
42
|
+
what was approved, verify, suggest the next step — **the single natural path
|
|
43
|
+
of action**, so that deviating from it is visible and recorded, never silent.
|
|
44
|
+
|
|
45
|
+
`stepgate` is *not* an enforcement tool. It never blocks your code, your
|
|
46
|
+
editor, your commits, or git. You can always edit anything, commit anything,
|
|
47
|
+
and cancel any agent session at any time. What it gives you is structure and
|
|
48
|
+
an honest, append-only trail of what was proposed, approved, executed, and
|
|
49
|
+
verified — across every agent working on the repo, even concurrently.
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pipx install stepgate # recommended: one global install per machine
|
|
55
|
+
# or: pip install stepgate
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Then, at the root of any project:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
stepgate init
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This creates `.stepgate/` (state + append-only history) and injects an
|
|
65
|
+
instruction block into `AGENTS.md`/`CLAUDE.md` telling agents to use the
|
|
66
|
+
protocol. It is idempotent and never overwrites anything you wrote in those
|
|
67
|
+
files — only the text between its own markers is ever touched. If your
|
|
68
|
+
project already has a domain guardrails document, point to it with
|
|
69
|
+
`--guardrails GUARDRAILS.md` and the generated block will reference it.
|
|
70
|
+
|
|
71
|
+
## The protocol
|
|
72
|
+
|
|
73
|
+
Every micro-change is a proposal moving through a state machine, validated by
|
|
74
|
+
the CLI itself:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
PENDING ──approve──► APPROVED ──exec-log──► EXECUTED ──verify──► VERIFIED ──close──► CLOSED
|
|
78
|
+
│
|
|
79
|
+
└─reject──► REJECTED (any non-terminal state) ──abandon──► ABANDONED
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
A proposal covers six points, each written as natural flowing prose (not a
|
|
83
|
+
telegraphic form): **what** will change now, **why** this step comes first,
|
|
84
|
+
**where** (files/contracts/flows touched), **how** it will be implemented,
|
|
85
|
+
the **expected result**, and the **verification** that will demonstrate it.
|
|
86
|
+
|
|
87
|
+
Key rule: *a micro-change reduces the scope of execution, never the depth of
|
|
88
|
+
investigation* — the agent still investigates everything it needs to
|
|
89
|
+
understand before proposing, even if it will only implement a small piece.
|
|
90
|
+
|
|
91
|
+
## A real cycle
|
|
92
|
+
|
|
93
|
+
You ask your agent to fix a race condition. It investigates, then proposes:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
stepgate propose --agent claude --file plan.json
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
You read it rendered as prose, and approve with a tweak:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
stepgate show
|
|
103
|
+
stepgate approve --adjust --note "rename the function to apply_sanity_loss_atomic"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The agent implements **only** what was approved, logs it (a `git diff --stat`
|
|
107
|
+
is captured automatically as objective evidence), and verifies:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
stepgate exec-log --summary "atomic decrement migration + typing" --files "migrations/013.sql,types.ts"
|
|
111
|
+
stepgate verify --evidence "type-check ok, simulated concurrency test passed"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
You close the cycle; the agent suggests — but does not start — the next step:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
stepgate close
|
|
118
|
+
stepgate next --suggest "wire SalaJogo.tsx to the new function via supabase.rpc(...)"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The suggestion stays visible in `stepgate status` until a new proposal is
|
|
122
|
+
opened. The full trail lives in `stepgate history` — chronological, across
|
|
123
|
+
all sessions and agents, append-only.
|
|
124
|
+
|
|
125
|
+
## Commands
|
|
126
|
+
|
|
127
|
+
| Command | Purpose |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| `stepgate init` | Create `.stepgate/`, inject the agent instruction block |
|
|
130
|
+
| `stepgate propose --agent X --file plan.json` | Register a micro-change plan (PENDING) |
|
|
131
|
+
| `stepgate show` | Render the active proposal as readable prose |
|
|
132
|
+
| `stepgate approve [--adjust --scope ... --note ...]` | Approve (optionally with reduced scope) |
|
|
133
|
+
| `stepgate reject --note "..."` | Reject a pending proposal |
|
|
134
|
+
| `stepgate exec-log --summary "..." --files "..."` | Record execution (+ automatic `git diff --stat`) |
|
|
135
|
+
| `stepgate verify --evidence "..."` | Record verification evidence |
|
|
136
|
+
| `stepgate close` | Close a verified micro-change |
|
|
137
|
+
| `stepgate abandon --reason "..."` | Cleanly abandon from any non-terminal state |
|
|
138
|
+
| `stepgate next --suggest "..."` | Record a next-step suggestion (opens nothing) |
|
|
139
|
+
| `stepgate status` | Current session + aggregated project view |
|
|
140
|
+
| `stepgate history [--session X] [--since DATE]` | Append-only, cross-session log |
|
|
141
|
+
| `stepgate doctor` | Report corrupted/invalid state files (fixes nothing) |
|
|
142
|
+
|
|
143
|
+
Multiple agents can work concurrently: each session has its own state file
|
|
144
|
+
(`.stepgate/sessions/claude-2026-07-09-1.json` — human-readable names, not
|
|
145
|
+
hashes), writes are file-locked, and `propose`/`status` warn — informationally,
|
|
146
|
+
never blockingly — when two active proposals touch the same files.
|
|
147
|
+
|
|
148
|
+
## Design principles
|
|
149
|
+
|
|
150
|
+
- **Never block.** No git hooks, no file locks on your code, no commit gates.
|
|
151
|
+
The file lock in `.stepgate/` protects only stepgate's own state files.
|
|
152
|
+
- **Never act silently.** No auto-repair, no auto-expiry of stale sessions,
|
|
153
|
+
no rewriting of history. `doctor` diagnoses; a human decides.
|
|
154
|
+
- **Non-interactive by design.** No prompts or confirmations, so it behaves
|
|
155
|
+
identically in a terminal, a desktop app, or an IDE side-panel extension.
|
|
156
|
+
- **Structure when you opt in.** Editing code without a proposal is a
|
|
157
|
+
legitimate flow, not an error. The state machine gives shape to the agent
|
|
158
|
+
workflow — it is never a requirement for the code to change.
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
[MIT](LICENSE) © Leo Costta
|
stepgate-0.1.0/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# stepgate
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/stepgate/)
|
|
4
|
+
[](https://pypi.org/project/stepgate/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
**A step-gated micro-change protocol for coding agents.**
|
|
8
|
+
|
|
9
|
+
Coding agents (Claude Code, Codex, …) working on large or loosely-scoped tasks
|
|
10
|
+
tend to mix contexts, silently expand scope, touch files unrelated to the
|
|
11
|
+
original request, or declare success without real evidence. `stepgate` makes
|
|
12
|
+
the correct workflow — investigate, propose, wait for approval, execute only
|
|
13
|
+
what was approved, verify, suggest the next step — **the single natural path
|
|
14
|
+
of action**, so that deviating from it is visible and recorded, never silent.
|
|
15
|
+
|
|
16
|
+
`stepgate` is *not* an enforcement tool. It never blocks your code, your
|
|
17
|
+
editor, your commits, or git. You can always edit anything, commit anything,
|
|
18
|
+
and cancel any agent session at any time. What it gives you is structure and
|
|
19
|
+
an honest, append-only trail of what was proposed, approved, executed, and
|
|
20
|
+
verified — across every agent working on the repo, even concurrently.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pipx install stepgate # recommended: one global install per machine
|
|
26
|
+
# or: pip install stepgate
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then, at the root of any project:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
stepgate init
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This creates `.stepgate/` (state + append-only history) and injects an
|
|
36
|
+
instruction block into `AGENTS.md`/`CLAUDE.md` telling agents to use the
|
|
37
|
+
protocol. It is idempotent and never overwrites anything you wrote in those
|
|
38
|
+
files — only the text between its own markers is ever touched. If your
|
|
39
|
+
project already has a domain guardrails document, point to it with
|
|
40
|
+
`--guardrails GUARDRAILS.md` and the generated block will reference it.
|
|
41
|
+
|
|
42
|
+
## The protocol
|
|
43
|
+
|
|
44
|
+
Every micro-change is a proposal moving through a state machine, validated by
|
|
45
|
+
the CLI itself:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
PENDING ──approve──► APPROVED ──exec-log──► EXECUTED ──verify──► VERIFIED ──close──► CLOSED
|
|
49
|
+
│
|
|
50
|
+
└─reject──► REJECTED (any non-terminal state) ──abandon──► ABANDONED
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
A proposal covers six points, each written as natural flowing prose (not a
|
|
54
|
+
telegraphic form): **what** will change now, **why** this step comes first,
|
|
55
|
+
**where** (files/contracts/flows touched), **how** it will be implemented,
|
|
56
|
+
the **expected result**, and the **verification** that will demonstrate it.
|
|
57
|
+
|
|
58
|
+
Key rule: *a micro-change reduces the scope of execution, never the depth of
|
|
59
|
+
investigation* — the agent still investigates everything it needs to
|
|
60
|
+
understand before proposing, even if it will only implement a small piece.
|
|
61
|
+
|
|
62
|
+
## A real cycle
|
|
63
|
+
|
|
64
|
+
You ask your agent to fix a race condition. It investigates, then proposes:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
stepgate propose --agent claude --file plan.json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
You read it rendered as prose, and approve with a tweak:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
stepgate show
|
|
74
|
+
stepgate approve --adjust --note "rename the function to apply_sanity_loss_atomic"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The agent implements **only** what was approved, logs it (a `git diff --stat`
|
|
78
|
+
is captured automatically as objective evidence), and verifies:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
stepgate exec-log --summary "atomic decrement migration + typing" --files "migrations/013.sql,types.ts"
|
|
82
|
+
stepgate verify --evidence "type-check ok, simulated concurrency test passed"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
You close the cycle; the agent suggests — but does not start — the next step:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
stepgate close
|
|
89
|
+
stepgate next --suggest "wire SalaJogo.tsx to the new function via supabase.rpc(...)"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The suggestion stays visible in `stepgate status` until a new proposal is
|
|
93
|
+
opened. The full trail lives in `stepgate history` — chronological, across
|
|
94
|
+
all sessions and agents, append-only.
|
|
95
|
+
|
|
96
|
+
## Commands
|
|
97
|
+
|
|
98
|
+
| Command | Purpose |
|
|
99
|
+
| --- | --- |
|
|
100
|
+
| `stepgate init` | Create `.stepgate/`, inject the agent instruction block |
|
|
101
|
+
| `stepgate propose --agent X --file plan.json` | Register a micro-change plan (PENDING) |
|
|
102
|
+
| `stepgate show` | Render the active proposal as readable prose |
|
|
103
|
+
| `stepgate approve [--adjust --scope ... --note ...]` | Approve (optionally with reduced scope) |
|
|
104
|
+
| `stepgate reject --note "..."` | Reject a pending proposal |
|
|
105
|
+
| `stepgate exec-log --summary "..." --files "..."` | Record execution (+ automatic `git diff --stat`) |
|
|
106
|
+
| `stepgate verify --evidence "..."` | Record verification evidence |
|
|
107
|
+
| `stepgate close` | Close a verified micro-change |
|
|
108
|
+
| `stepgate abandon --reason "..."` | Cleanly abandon from any non-terminal state |
|
|
109
|
+
| `stepgate next --suggest "..."` | Record a next-step suggestion (opens nothing) |
|
|
110
|
+
| `stepgate status` | Current session + aggregated project view |
|
|
111
|
+
| `stepgate history [--session X] [--since DATE]` | Append-only, cross-session log |
|
|
112
|
+
| `stepgate doctor` | Report corrupted/invalid state files (fixes nothing) |
|
|
113
|
+
|
|
114
|
+
Multiple agents can work concurrently: each session has its own state file
|
|
115
|
+
(`.stepgate/sessions/claude-2026-07-09-1.json` — human-readable names, not
|
|
116
|
+
hashes), writes are file-locked, and `propose`/`status` warn — informationally,
|
|
117
|
+
never blockingly — when two active proposals touch the same files.
|
|
118
|
+
|
|
119
|
+
## Design principles
|
|
120
|
+
|
|
121
|
+
- **Never block.** No git hooks, no file locks on your code, no commit gates.
|
|
122
|
+
The file lock in `.stepgate/` protects only stepgate's own state files.
|
|
123
|
+
- **Never act silently.** No auto-repair, no auto-expiry of stale sessions,
|
|
124
|
+
no rewriting of history. `doctor` diagnoses; a human decides.
|
|
125
|
+
- **Non-interactive by design.** No prompts or confirmations, so it behaves
|
|
126
|
+
identically in a terminal, a desktop app, or an IDE side-panel extension.
|
|
127
|
+
- **Structure when you opt in.** Editing code without a proposal is a
|
|
128
|
+
legitimate flow, not an error. The state machine gives shape to the agent
|
|
129
|
+
workflow — it is never a requirement for the code to change.
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
[MIT](LICENSE) © Leo Costta
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "stepgate"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "A step-gated micro-change protocol CLI for coding agents: propose, approve, execute, verify — one small step at a time."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
authors = [{ name = "Leo Costta" }]
|
|
14
|
+
keywords = ["cli", "coding-agents", "workflow", "claude-code", "codex", "guardrails"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Programming Language :: Python :: 3.14",
|
|
26
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"rich>=13.0",
|
|
30
|
+
"filelock>=3.12",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/LeoCostta/stepgate"
|
|
35
|
+
Repository = "https://github.com/LeoCostta/stepgate"
|
|
36
|
+
Issues = "https://github.com/LeoCostta/stepgate/issues"
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
stepgate = "stepgate.cli:main"
|
|
40
|
+
|
|
41
|
+
[project.optional-dependencies]
|
|
42
|
+
dev = ["pytest>=8.0"]
|
|
43
|
+
|
|
44
|
+
[tool.hatch.version]
|
|
45
|
+
path = "src/stepgate/__init__.py"
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.sdist]
|
|
48
|
+
exclude = [".stepgate", "AGENTS.md", "CLAUDE.md", ".gitignore"]
|
|
49
|
+
|
|
50
|
+
[tool.pytest.ini_options]
|
|
51
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""stepgate command-line interface.
|
|
2
|
+
|
|
3
|
+
All commands are non-interactive by design (no prompts, no confirmations) so
|
|
4
|
+
they behave identically in a terminal, a desktop app, or an IDE side-panel
|
|
5
|
+
extension. User-facing errors are printed as clear messages, never as raw
|
|
6
|
+
Python tracebacks.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
from stepgate import __version__
|
|
15
|
+
from stepgate.model import StepgateError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _add_session_flags(parser: argparse.ArgumentParser) -> None:
|
|
19
|
+
parser.add_argument("--agent", help="calling agent name (e.g. claude, codex)")
|
|
20
|
+
parser.add_argument("--session", help="explicit session name to target")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
24
|
+
parser = argparse.ArgumentParser(
|
|
25
|
+
prog="stepgate",
|
|
26
|
+
description=(
|
|
27
|
+
"Step-gated micro-change protocol for coding agents: propose, "
|
|
28
|
+
"approve, execute, verify - one small step at a time. stepgate "
|
|
29
|
+
"structures the flow; it never blocks your code, your commits, "
|
|
30
|
+
"or your git."
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument("--version", action="version", version=f"stepgate {__version__}")
|
|
34
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
35
|
+
|
|
36
|
+
p = sub.add_parser("init", help="create .stepgate/ and inject the agent instruction block")
|
|
37
|
+
p.add_argument("--guardrails", help="path to an existing domain guardrails doc to reference")
|
|
38
|
+
|
|
39
|
+
p = sub.add_parser("propose", help="register a micro-change plan (state: PENDING)")
|
|
40
|
+
p.add_argument("--file", required=True, help="JSON file with the six plan fields")
|
|
41
|
+
p.add_argument("--new-session", action="store_true", help="start a fresh session")
|
|
42
|
+
_add_session_flags(p)
|
|
43
|
+
|
|
44
|
+
p = sub.add_parser("show", help="show the active proposal rendered as prose")
|
|
45
|
+
_add_session_flags(p)
|
|
46
|
+
|
|
47
|
+
p = sub.add_parser("approve", help="PENDING -> APPROVED")
|
|
48
|
+
p.add_argument("--adjust", action="store_true", help="approve with an adjusted/reduced scope")
|
|
49
|
+
p.add_argument("--scope", help="comma-separated adjusted scope (files/areas)")
|
|
50
|
+
p.add_argument("--note", help="note describing the adjustment")
|
|
51
|
+
_add_session_flags(p)
|
|
52
|
+
|
|
53
|
+
p = sub.add_parser("reject", help="PENDING -> REJECTED")
|
|
54
|
+
p.add_argument("--note", required=True, help="reason for rejection")
|
|
55
|
+
_add_session_flags(p)
|
|
56
|
+
|
|
57
|
+
p = sub.add_parser("exec-log", help="APPROVED -> EXECUTED (consolidated summary)")
|
|
58
|
+
p.add_argument("--summary", required=True, help="what was done, consolidated")
|
|
59
|
+
p.add_argument("--files", help="comma-separated files that were touched")
|
|
60
|
+
_add_session_flags(p)
|
|
61
|
+
|
|
62
|
+
p = sub.add_parser("verify", help="EXECUTED -> VERIFIED")
|
|
63
|
+
p.add_argument("--evidence", required=True, help="tests/runs/evidence demonstrating the result")
|
|
64
|
+
_add_session_flags(p)
|
|
65
|
+
|
|
66
|
+
p = sub.add_parser("close", help="VERIFIED -> CLOSED")
|
|
67
|
+
_add_session_flags(p)
|
|
68
|
+
|
|
69
|
+
p = sub.add_parser("abandon", help="any non-terminal state -> ABANDONED")
|
|
70
|
+
p.add_argument("--reason", required=True, help="why the proposal is being abandoned")
|
|
71
|
+
_add_session_flags(p)
|
|
72
|
+
|
|
73
|
+
p = sub.add_parser("next", help="record a next-step suggestion (does not open a proposal)")
|
|
74
|
+
p.add_argument("--suggest", required=True, help="the suggested next step")
|
|
75
|
+
_add_session_flags(p)
|
|
76
|
+
|
|
77
|
+
p = sub.add_parser("status", help="current session state + aggregated project view")
|
|
78
|
+
_add_session_flags(p)
|
|
79
|
+
|
|
80
|
+
p = sub.add_parser("history", help="chronological, cross-session log")
|
|
81
|
+
p.add_argument("--session", help="filter by session name")
|
|
82
|
+
p.add_argument("--since", help="filter by ISO date (e.g. 2026-07-09)")
|
|
83
|
+
|
|
84
|
+
sub.add_parser("doctor", help="scan .stepgate/ and report problems (never fixes anything)")
|
|
85
|
+
|
|
86
|
+
return parser
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def main(argv: list[str] | None = None) -> int:
|
|
90
|
+
# Consoles with limited encodings (e.g. cp1252 on legacy Windows) must
|
|
91
|
+
# degrade output gracefully, never crash with a UnicodeEncodeError.
|
|
92
|
+
for stream in (sys.stdout, sys.stderr):
|
|
93
|
+
if hasattr(stream, "reconfigure"):
|
|
94
|
+
try:
|
|
95
|
+
stream.reconfigure(errors="replace")
|
|
96
|
+
except (OSError, ValueError):
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
args = build_parser().parse_args(argv)
|
|
100
|
+
|
|
101
|
+
from stepgate.commands import doctor, init_cmd, lifecycle, views
|
|
102
|
+
|
|
103
|
+
handlers = {
|
|
104
|
+
"init": init_cmd.cmd_init,
|
|
105
|
+
"propose": lifecycle.cmd_propose,
|
|
106
|
+
"show": views.cmd_show,
|
|
107
|
+
"approve": lifecycle.cmd_approve,
|
|
108
|
+
"reject": lifecycle.cmd_reject,
|
|
109
|
+
"exec-log": lifecycle.cmd_exec_log,
|
|
110
|
+
"verify": lifecycle.cmd_verify,
|
|
111
|
+
"close": lifecycle.cmd_close,
|
|
112
|
+
"abandon": lifecycle.cmd_abandon,
|
|
113
|
+
"next": lifecycle.cmd_next,
|
|
114
|
+
"status": views.cmd_status,
|
|
115
|
+
"history": views.cmd_history,
|
|
116
|
+
"doctor": doctor.cmd_doctor,
|
|
117
|
+
}
|
|
118
|
+
try:
|
|
119
|
+
return handlers[args.command](args)
|
|
120
|
+
except StepgateError as exc:
|
|
121
|
+
print(f"stepgate: error: {exc}", file=sys.stderr)
|
|
122
|
+
return 1
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
if __name__ == "__main__":
|
|
126
|
+
raise SystemExit(main())
|
|
File without changes
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""stepgate doctor: scan .stepgate/ and report problems without fixing
|
|
2
|
+
anything. A diagnostic tool, never a repair tool — and never a gate: even
|
|
3
|
+
when it finds problems, nothing else is blocked."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
from stepgate import render
|
|
10
|
+
from stepgate.model import Session
|
|
11
|
+
from stepgate.store import Store
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def cmd_doctor(args) -> int:
|
|
15
|
+
store = Store.find()
|
|
16
|
+
problems: list[str] = []
|
|
17
|
+
|
|
18
|
+
if store.config_path.exists():
|
|
19
|
+
try:
|
|
20
|
+
config = json.loads(store.config_path.read_text(encoding="utf-8"))
|
|
21
|
+
for key in ("project_name", "agents", "verify_command"):
|
|
22
|
+
if key not in config:
|
|
23
|
+
problems.append(f"{store.config_path}: missing expected key '{key}'")
|
|
24
|
+
except (json.JSONDecodeError, UnicodeDecodeError) as exc:
|
|
25
|
+
problems.append(f"{store.config_path}: not valid JSON ({exc})")
|
|
26
|
+
else:
|
|
27
|
+
problems.append(f"{store.config_path}: missing (run 'stepgate init' to recreate it)")
|
|
28
|
+
|
|
29
|
+
if store.sessions_dir.is_dir():
|
|
30
|
+
for path in sorted(store.sessions_dir.glob("*.json")):
|
|
31
|
+
try:
|
|
32
|
+
Session.from_dict(json.loads(path.read_text(encoding="utf-8")))
|
|
33
|
+
except (json.JSONDecodeError, UnicodeDecodeError, KeyError, TypeError) as exc:
|
|
34
|
+
problems.append(f"{path}: corrupted or invalid ({exc.__class__.__name__}: {exc})")
|
|
35
|
+
else:
|
|
36
|
+
problems.append(f"{store.sessions_dir}: missing directory")
|
|
37
|
+
|
|
38
|
+
if store.history_path.exists():
|
|
39
|
+
for i, line in enumerate(
|
|
40
|
+
store.history_path.read_text(encoding="utf-8").splitlines(), start=1
|
|
41
|
+
):
|
|
42
|
+
if not line.strip():
|
|
43
|
+
continue
|
|
44
|
+
try:
|
|
45
|
+
json.loads(line)
|
|
46
|
+
except json.JSONDecodeError:
|
|
47
|
+
problems.append(f"{store.history_path}: invalid JSON at line {i}")
|
|
48
|
+
else:
|
|
49
|
+
problems.append(f"{store.history_path}: missing file")
|
|
50
|
+
|
|
51
|
+
if not problems:
|
|
52
|
+
render.info(f"[green]doctor: no problems found in {store.dir}[/]")
|
|
53
|
+
return 0
|
|
54
|
+
render.info(f"[yellow]doctor: found {len(problems)} problem(s) in {store.dir}:[/]")
|
|
55
|
+
for problem in problems:
|
|
56
|
+
render.info(f" - {problem}")
|
|
57
|
+
render.info(
|
|
58
|
+
"[dim]stepgate never repairs state automatically - please inspect the "
|
|
59
|
+
"files above manually. Other sessions and normal project work are "
|
|
60
|
+
"not blocked by this.[/]"
|
|
61
|
+
)
|
|
62
|
+
return 1
|