sessionsteward 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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Calas Automations Inc.
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,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: sessionsteward
3
+ Version: 0.1.0
4
+ Summary: Versioned snapshots for agent working state: instant undo for CLAUDE.md and friends.
5
+ Author: Calas Automations Inc.
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/calasdan-sketch/sessionsteward
8
+ Project-URL: Issues, https://github.com/calasdan-sketch/sessionsteward/issues
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Dynamic: license-file
13
+
14
+ # Session Steward (`sessionsteward`)
15
+
16
+ Versioned snapshots for agent working state. Coding agents silently
17
+ degrade: compaction drops constraints, and one "remember this" can
18
+ overwrite your 30-line CLAUDE.md with a single bullet — no warning, no
19
+ undo. sessionsteward git-versions your agent config + memory files on
20
+ every change: instant undo, pre-compaction snapshots, and a context-diet
21
+ report showing what's eating the window.
22
+
23
+ **Scope:** session *ops* (snapshots, undo, compaction guard) — not memory
24
+ *storage*. That boundary stays clean.
25
+
26
+ ## Install
27
+
28
+ Zero dependencies — Python 3.10+ stdlib only (uses your system's git).
29
+
30
+ ```bash
31
+ git clone <repo> && cd sessionsteward
32
+ # no-install alias (run from the repo root):
33
+ alias sessionsteward="python3 -m sessionsteward"
34
+ ```
35
+
36
+ Or install it properly (console script `sessionsteward`):
37
+
38
+ ```bash
39
+ pip install .
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ ```bash
45
+ sessionsteward init # set up the snapshots repo
46
+ sessionsteward watch --add NOTES.md ~/x.md # watch extra paths
47
+
48
+ sessionsteward snap -m "before refactor" # snapshot now
49
+ sessionsteward precompact # snapshot before compaction
50
+ sessionsteward watch # daemon: auto-snapshot on change
51
+
52
+ sessionsteward log # list snapshots (this project only)
53
+ sessionsteward diff # what changed (e.g. what compaction dropped)
54
+ sessionsteward undo # restore the previous snapshot
55
+ sessionsteward restore <sha> # restore a specific snapshot
56
+ sessionsteward diet # what's eating the context window
57
+ sessionsteward install-hooks # print Claude Code hooks JSON
58
+ sessionsteward install-hooks --write # merge it into ~/.claude/settings.json
59
+ ```
60
+
61
+ The horror-story scenario, fixed:
62
+
63
+ ```bash
64
+ $ sessionsteward snap -m "before letting the agent edit memory"
65
+ $ # agent overwrites your 30-line CLAUDE.md with one bullet
66
+ $ sessionsteward undo
67
+ undone 1 step(s) -> a3f9c21
68
+ /home/you/proj/CLAUDE.md
69
+ ```
70
+
71
+ Safety rules: empty snaps are skipped (no noise commits); `undo`/`restore`
72
+ never delete — files that didn't exist in the target snapshot are moved to
73
+ `~/.sessionsteward/trash/` instead, **directories included**.
74
+ `log`/`diff`/`undo`/`restore` are scoped to the current project (`--root`,
75
+ default: cwd): snapshots record their project root, and a `restore` of a
76
+ snapshot from another project is refused rather than clobbering your files.
77
+ Undoing (or restoring) with unsaved changes first snapshots the current
78
+ state as `pre-undo`/`pre-restore`, so every undo is reversible — redo is
79
+ `restore` of that snapshot.
80
+
81
+ Ignored on every snap and by the watch daemon: `*.log`, `*transcript*`,
82
+ `__pycache__`, `.git`, `node_modules` (transcripts would otherwise balloon
83
+ the snapshots repo).
84
+
85
+ ## Claude Code hooks
86
+
87
+ ```bash
88
+ sessionsteward install-hooks # print the hooks JSON + where it goes
89
+ sessionsteward install-hooks --write # merge into ~/.claude/settings.json
90
+ ```
91
+
92
+ This wires **PreCompact** → `sessionsteward precompact` and **SessionStart**
93
+ → `sessionsteward snap -m "session start"`. `--write` merges into the
94
+ existing settings file without clobbering your other hooks (re-running is
95
+ a no-op).
96
+
97
+ ## Context diet
98
+
99
+ ```
100
+ # Context diet
101
+
102
+ Watched files: 3, total ~12,400 chars (~3,100 tokens)
103
+
104
+ ## Biggest files
105
+ | file | chars | lines |
106
+ ...
107
+ ## Duplicated content (4 lines in 2+ files)
108
+ - _Be helpful and concise in all responses_ …
109
+ - `/proj/CLAUDE.md`
110
+ - `/proj/.claude/settings.json`
111
+ ```
112
+
113
+ ## Suggested hook wiring
114
+
115
+ - **Pre-compaction:** run `sessionsteward precompact` (or add it to your
116
+ agent's pre-compact hook) — then `sessionsteward diff` afterwards to see
117
+ exactly what the compaction dropped.
118
+ - **Session start:** `sessionsteward snap -m "session start"`.
119
+ - **Memory writes:** point the steward at the same files your agent's
120
+ memory hook writes.
121
+
122
+ ## Tests
123
+
124
+ ```bash
125
+ python3 -m unittest discover -s tests # 24 tests, temp dirs only
126
+ ```
127
+
128
+ ## License
129
+
130
+ TBD
@@ -0,0 +1,117 @@
1
+ # Session Steward (`sessionsteward`)
2
+
3
+ Versioned snapshots for agent working state. Coding agents silently
4
+ degrade: compaction drops constraints, and one "remember this" can
5
+ overwrite your 30-line CLAUDE.md with a single bullet — no warning, no
6
+ undo. sessionsteward git-versions your agent config + memory files on
7
+ every change: instant undo, pre-compaction snapshots, and a context-diet
8
+ report showing what's eating the window.
9
+
10
+ **Scope:** session *ops* (snapshots, undo, compaction guard) — not memory
11
+ *storage*. That boundary stays clean.
12
+
13
+ ## Install
14
+
15
+ Zero dependencies — Python 3.10+ stdlib only (uses your system's git).
16
+
17
+ ```bash
18
+ git clone <repo> && cd sessionsteward
19
+ # no-install alias (run from the repo root):
20
+ alias sessionsteward="python3 -m sessionsteward"
21
+ ```
22
+
23
+ Or install it properly (console script `sessionsteward`):
24
+
25
+ ```bash
26
+ pip install .
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ```bash
32
+ sessionsteward init # set up the snapshots repo
33
+ sessionsteward watch --add NOTES.md ~/x.md # watch extra paths
34
+
35
+ sessionsteward snap -m "before refactor" # snapshot now
36
+ sessionsteward precompact # snapshot before compaction
37
+ sessionsteward watch # daemon: auto-snapshot on change
38
+
39
+ sessionsteward log # list snapshots (this project only)
40
+ sessionsteward diff # what changed (e.g. what compaction dropped)
41
+ sessionsteward undo # restore the previous snapshot
42
+ sessionsteward restore <sha> # restore a specific snapshot
43
+ sessionsteward diet # what's eating the context window
44
+ sessionsteward install-hooks # print Claude Code hooks JSON
45
+ sessionsteward install-hooks --write # merge it into ~/.claude/settings.json
46
+ ```
47
+
48
+ The horror-story scenario, fixed:
49
+
50
+ ```bash
51
+ $ sessionsteward snap -m "before letting the agent edit memory"
52
+ $ # agent overwrites your 30-line CLAUDE.md with one bullet
53
+ $ sessionsteward undo
54
+ undone 1 step(s) -> a3f9c21
55
+ /home/you/proj/CLAUDE.md
56
+ ```
57
+
58
+ Safety rules: empty snaps are skipped (no noise commits); `undo`/`restore`
59
+ never delete — files that didn't exist in the target snapshot are moved to
60
+ `~/.sessionsteward/trash/` instead, **directories included**.
61
+ `log`/`diff`/`undo`/`restore` are scoped to the current project (`--root`,
62
+ default: cwd): snapshots record their project root, and a `restore` of a
63
+ snapshot from another project is refused rather than clobbering your files.
64
+ Undoing (or restoring) with unsaved changes first snapshots the current
65
+ state as `pre-undo`/`pre-restore`, so every undo is reversible — redo is
66
+ `restore` of that snapshot.
67
+
68
+ Ignored on every snap and by the watch daemon: `*.log`, `*transcript*`,
69
+ `__pycache__`, `.git`, `node_modules` (transcripts would otherwise balloon
70
+ the snapshots repo).
71
+
72
+ ## Claude Code hooks
73
+
74
+ ```bash
75
+ sessionsteward install-hooks # print the hooks JSON + where it goes
76
+ sessionsteward install-hooks --write # merge into ~/.claude/settings.json
77
+ ```
78
+
79
+ This wires **PreCompact** → `sessionsteward precompact` and **SessionStart**
80
+ → `sessionsteward snap -m "session start"`. `--write` merges into the
81
+ existing settings file without clobbering your other hooks (re-running is
82
+ a no-op).
83
+
84
+ ## Context diet
85
+
86
+ ```
87
+ # Context diet
88
+
89
+ Watched files: 3, total ~12,400 chars (~3,100 tokens)
90
+
91
+ ## Biggest files
92
+ | file | chars | lines |
93
+ ...
94
+ ## Duplicated content (4 lines in 2+ files)
95
+ - _Be helpful and concise in all responses_ …
96
+ - `/proj/CLAUDE.md`
97
+ - `/proj/.claude/settings.json`
98
+ ```
99
+
100
+ ## Suggested hook wiring
101
+
102
+ - **Pre-compaction:** run `sessionsteward precompact` (or add it to your
103
+ agent's pre-compact hook) — then `sessionsteward diff` afterwards to see
104
+ exactly what the compaction dropped.
105
+ - **Session start:** `sessionsteward snap -m "session start"`.
106
+ - **Memory writes:** point the steward at the same files your agent's
107
+ memory hook writes.
108
+
109
+ ## Tests
110
+
111
+ ```bash
112
+ python3 -m unittest discover -s tests # 24 tests, temp dirs only
113
+ ```
114
+
115
+ ## License
116
+
117
+ TBD
@@ -0,0 +1,20 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sessionsteward"
7
+ version = "0.1.0"
8
+ description = "Versioned snapshots for agent working state: instant undo for CLAUDE.md and friends."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ authors = [{name = "Calas Automations Inc."}]
12
+ license = {text = "MIT"}
13
+
14
+
15
+ [project.urls]
16
+ Homepage = "https://github.com/calasdan-sketch/sessionsteward"
17
+ Issues = "https://github.com/calasdan-sketch/sessionsteward/issues"
18
+
19
+ [project.scripts]
20
+ sessionsteward = "sessionsteward.cli:main"
File without changes
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -0,0 +1,244 @@
1
+ """Session steward: versioned snapshots for agent working state.
2
+
3
+ Coding agents silently degrade: compaction drops constraints, and one
4
+ "remember this" can overwrite your 30-line CLAUDE.md with a single
5
+ bullet — no warning, no undo. sessionsteward git-versions your agent
6
+ config + memory files on every change. Instant undo, pre-compaction
7
+ snapshots, and a context-diet report.
8
+
9
+ Scope note: this is *session ops* (snapshots, undo, compaction guard).
10
+ Memory *storage* is OneMemory's job — the boundary stays clean.
11
+
12
+ sessionsteward init # set up the snapshots repo
13
+ sessionsteward watch --add CLAUDE.md # watch extra paths (also: ~/..., /abs/...)
14
+ sessionsteward snap -m "before refactor"
15
+ sessionsteward precompact # snapshot before compaction
16
+ sessionsteward log # list snapshots
17
+ sessionsteward diff [sha1] [sha2] # what changed (e.g. what compaction dropped)
18
+ sessionsteward undo # restore the previous snapshot
19
+ sessionsteward restore <sha> # restore a specific snapshot
20
+ sessionsteward diet # what's eating the context window
21
+ sessionsteward install-hooks [--write] # Claude Code hooks JSON
22
+ sessionsteward watch # daemon: auto-snapshot on change
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import argparse
28
+ import json
29
+ import os
30
+ import sys
31
+ import time
32
+
33
+ from . import core as core_mod
34
+ from . import diet as diet_mod
35
+
36
+ VERSION = "0.1.0"
37
+
38
+ CLAUDE_SETTINGS = os.path.expanduser("~/.claude/settings.json")
39
+
40
+
41
+ def cmd_init(args) -> int:
42
+ repo = core_mod.init_repo()
43
+ print(f"snapshots repo: {repo}")
44
+ print("watching (defaults): " + ", ".join(core_mod.DEFAULT_WATCH))
45
+ print("add more: sessionsteward watch --add <path>")
46
+ return 0
47
+
48
+
49
+ def cmd_watch_add(args) -> int:
50
+ core_mod.init_repo()
51
+ cfg = core_mod.load_config()
52
+ for p in args.add:
53
+ if p not in cfg["watch"]:
54
+ cfg["watch"].append(p)
55
+ core_mod.save_config(cfg)
56
+ print("watching: " + ", ".join(cfg["watch"] or core_mod.DEFAULT_WATCH))
57
+ return 0
58
+
59
+
60
+ def cmd_snap(args) -> int:
61
+ sha = core_mod.snap(args.root, args.message or "manual snapshot")
62
+ print(f"snapshot {sha[:7]}")
63
+ return 0
64
+
65
+
66
+ def cmd_precompact(args) -> int:
67
+ sha = core_mod.snap(args.root, "pre-compaction snapshot")
68
+ print(f"pre-compaction snapshot {sha[:7]} — "
69
+ f"`sessionsteward diff {sha[:7]}` after, to see what was dropped")
70
+ return 0
71
+
72
+
73
+ def cmd_log(args) -> int:
74
+ if not core_mod.repo_ready():
75
+ print("no snapshots yet — run `sessionsteward init` first")
76
+ return 0
77
+ for s in core_mod.log(args.limit, project_root=args.root):
78
+ print(f"{s['sha'][:7]} {s['date'][:16]} {s['message']}")
79
+ return 0
80
+
81
+
82
+ def cmd_diff(args) -> int:
83
+ if not core_mod.repo_ready():
84
+ print("no snapshots yet")
85
+ return 2
86
+ snaps = core_mod.log(50, project_root=args.root)
87
+ sha1 = args.sha1 or (snaps[1]["sha"] if len(snaps) > 1 else None)
88
+ sha2 = args.sha2 or "HEAD"
89
+ if not sha1:
90
+ print("only one snapshot — nothing to diff")
91
+ return 0
92
+ out = core_mod.diff(sha1, sha2)
93
+ kept = [ln for ln in out.splitlines() if ln[:1] in "+-"
94
+ and not ln.startswith(("+++", "---"))]
95
+ sys.stdout.write("\n".join(kept[:200]) + ("\n" if kept else "(no changes)\n"))
96
+ return 0
97
+
98
+
99
+ def cmd_undo(args) -> int:
100
+ if not core_mod.repo_ready():
101
+ print("no snapshots yet")
102
+ return 2
103
+ snaps = core_mod.log(50, project_root=args.root)
104
+ if not snaps:
105
+ print("nothing to restore — no snapshots yet")
106
+ return 1
107
+ if core_mod.worktree_matches_snapshot(snaps[0]["sha"], args.root):
108
+ # Clean state: the current state is already snapshotted, so undo
109
+ # needs no pre-snap and redo is just `restore` of snaps[0].
110
+ if len(snaps) < 2:
111
+ print("only one snapshot — nothing to undo to")
112
+ return 0
113
+ target = snaps[min(args.steps, len(snaps) - 1)]["sha"]
114
+ else:
115
+ # Dirty state: snapshot it first ("pre-undo") so undo is
116
+ # reversible — redo is `restore` of the pre-undo snapshot.
117
+ core_mod.snap(args.root, "pre-undo")
118
+ snaps = core_mod.log(50, project_root=args.root)
119
+ target = snaps[min(args.steps, len(snaps) - 1)]["sha"]
120
+ try:
121
+ restored = core_mod.restore(target, project_root=args.root)
122
+ except core_mod.NothingToRestoreError as e:
123
+ print(e)
124
+ return 1
125
+ except core_mod.CrossProjectError as e:
126
+ print(f"error: {e}")
127
+ return 1
128
+ print(f"undone {args.steps} step(s) -> {target[:7]}")
129
+ for r in restored:
130
+ print(f" {r}")
131
+ return 0
132
+
133
+
134
+ def cmd_restore(args) -> int:
135
+ if not core_mod.repo_ready():
136
+ print("no snapshots yet")
137
+ return 2
138
+ # Validate before the pre-restore snap so a bad sha doesn't pollute log.
139
+ try:
140
+ core_mod.check_restore(args.sha, args.root)
141
+ except core_mod.NothingToRestoreError as e:
142
+ print(e)
143
+ return 1
144
+ except core_mod.CrossProjectError as e:
145
+ print(f"error: {e}")
146
+ return 1
147
+ # Redo support: if the current state isn't snapshotted yet, snapshot
148
+ # it first ("pre-restore") so the restore is reversible.
149
+ snaps = core_mod.log(50, project_root=args.root)
150
+ if snaps and not core_mod.worktree_matches_snapshot(snaps[0]["sha"],
151
+ args.root):
152
+ core_mod.snap(args.root, "pre-restore")
153
+ restored = core_mod.restore(args.sha, project_root=args.root)
154
+ print(f"restored {args.sha[:7]}")
155
+ for r in restored:
156
+ print(f" {r}")
157
+ return 0
158
+
159
+
160
+ def cmd_diet(args) -> int:
161
+ print(diet_mod.render(diet_mod.analyze(args.root)))
162
+ return 0
163
+
164
+
165
+ def cmd_install_hooks(args) -> int:
166
+ payload = core_mod.build_hooks_json()
167
+ print(json.dumps(payload, indent=2))
168
+ print()
169
+ print("Copy the JSON above into the \"hooks\" section of "
170
+ "~/.claude/settings.json, or re-run with --write to merge it "
171
+ "automatically (existing settings are preserved).")
172
+ print("Wiring: PreCompact -> `sessionsteward precompact`, "
173
+ "SessionStart -> `sessionsteward snap -m \"session start\"`.")
174
+ if args.write:
175
+ path = core_mod.write_hooks_file(CLAUDE_SETTINGS)
176
+ print(f"merged hooks into {path}")
177
+ return 0
178
+
179
+
180
+ def cmd_watch(args) -> int:
181
+ core_mod.init_repo()
182
+ print(f"watching for changes (every {args.interval}s, Ctrl-C to stop)…")
183
+ mtimes = core_mod.file_mtimes(core_mod.watched_paths(args.root))
184
+ try:
185
+ while True:
186
+ time.sleep(args.interval)
187
+ now = core_mod.file_mtimes(core_mod.watched_paths(args.root))
188
+ if now != mtimes:
189
+ sha = core_mod.snap(args.root, "auto: files changed")
190
+ print(f"change detected -> snapshot {sha[:7]}")
191
+ mtimes = now
192
+ except KeyboardInterrupt:
193
+ print("\nstopped.")
194
+ return 0
195
+
196
+
197
+ def build_parser() -> argparse.ArgumentParser:
198
+ p = argparse.ArgumentParser(prog="sessionsteward", description=__doc__)
199
+ p.add_argument("--version", action="version", version=f"%(prog)s {VERSION}")
200
+ p.add_argument("--root", default=os.getcwd(),
201
+ help="Project root for relative watch paths.")
202
+ sub = p.add_subparsers(dest="cmd", required=True)
203
+
204
+ sub.add_parser("init", help="Set up the snapshots repo.")
205
+ wa = sub.add_parser("watch", help="Watch daemon / manage watch list.")
206
+ wa.add_argument("--add", nargs="+", default=None,
207
+ help="Add paths to the watch list (then exit).")
208
+ wa.add_argument("--interval", type=float, default=5.0,
209
+ help="Poll seconds for the daemon.")
210
+ sn = sub.add_parser("snap", help="Snapshot now.")
211
+ sn.add_argument("-m", "--message", default=None)
212
+ sub.add_parser("precompact", help="Snapshot before compaction.")
213
+ lg = sub.add_parser("log", help="List snapshots.")
214
+ lg.add_argument("--limit", type=int, default=20)
215
+ df = sub.add_parser("diff", help="Diff two snapshots.")
216
+ df.add_argument("sha1", nargs="?")
217
+ df.add_argument("sha2", nargs="?", default="HEAD")
218
+ un = sub.add_parser("undo", help="Restore the previous snapshot.")
219
+ un.add_argument("--steps", type=int, default=1)
220
+ rs = sub.add_parser("restore", help="Restore a specific snapshot.")
221
+ rs.add_argument("sha")
222
+ sub.add_parser("diet", help="Context-diet report.")
223
+ ih = sub.add_parser("install-hooks",
224
+ help="Print (or --write) Claude Code hooks JSON.")
225
+ ih.add_argument("--write", action="store_true",
226
+ help="Merge the hooks into ~/.claude/settings.json.")
227
+ return p
228
+
229
+
230
+ def main(argv=None) -> int:
231
+ args = build_parser().parse_args(argv)
232
+ if args.cmd == "watch" and args.add:
233
+ return cmd_watch_add(args)
234
+ cmds = {
235
+ "init": cmd_init, "watch": cmd_watch, "snap": cmd_snap,
236
+ "precompact": cmd_precompact, "log": cmd_log, "diff": cmd_diff,
237
+ "undo": cmd_undo, "restore": cmd_restore, "diet": cmd_diet,
238
+ "install-hooks": cmd_install_hooks,
239
+ }
240
+ return cmds[args.cmd](args)
241
+
242
+
243
+ if __name__ == "__main__":
244
+ sys.exit(main())