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.
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ build/
6
+ dist/
7
+ .venv/
8
+ venv/
9
+ .pytest_cache/
10
+ .coverage
11
+ htmlcov/
12
+ .stepgate/.lock
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.
@@ -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
+ [![PyPI version](https://img.shields.io/pypi/v/stepgate)](https://pypi.org/project/stepgate/)
33
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue)](https://pypi.org/project/stepgate/)
34
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](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
@@ -0,0 +1,133 @@
1
+ # stepgate
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/stepgate)](https://pypi.org/project/stepgate/)
4
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue)](https://pypi.org/project/stepgate/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](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,3 @@
1
+ """stepgate: a step-gated micro-change protocol CLI for coding agents."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from stepgate.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -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