lockmaster 0.0.1__tar.gz → 0.0.2__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.
- {lockmaster-0.0.1 → lockmaster-0.0.2}/PKG-INFO +3 -3
- {lockmaster-0.0.1 → lockmaster-0.0.2}/README.md +2 -2
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/auto-land.py +24 -6
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/now-ledger.py +23 -5
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/skills/landing/SKILL.md +5 -3
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/__init__.py +1 -1
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster.egg-info/PKG-INFO +3 -3
- {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_kit_hooks.py +48 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/LICENSE +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/block-main.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/claim-guard.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/disk-budget.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/guard-open-work-inbox.sh +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/inject-inbox.sh +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/release-locks.sh +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/require-progress.sh +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/session-lock.sh +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/stop-if-blocked.sh +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/work-files.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/worktree-cleanup.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/settings.json +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/skills/docket/SKILL.md +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/work/BACKLOG.md +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/work/INTERVIEW.md +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/work/LESSONS.md +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/work/MACHINE.md +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/work/OPEN-WORK.md +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/work/WORKLOG.md +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/pyproject.toml +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/setup.cfg +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/__main__.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/cli.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/config.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/doctor.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/drivers.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/generated.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/git.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/install.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/land.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/merge.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/now.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/repo.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/status.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/surface.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster.egg-info/SOURCES.txt +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster.egg-info/dependency_links.txt +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster.egg-info/entry_points.txt +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster.egg-info/top_level.txt +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_autoland.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_cli.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_config.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_gate_hooks.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_hooks.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_install.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_land.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_merge.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_packaging.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_parallel.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_regen.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_release.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_status.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_waiting.py +0 -0
- {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_work_files.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lockmaster
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
4
4
|
Summary: Lands AI-agent changes into main one at a time, without ever losing work.
|
|
5
5
|
Author: Arpit Arya
|
|
6
6
|
License-Expression: MIT
|
|
@@ -34,7 +34,7 @@ lockmaster puts the changes onto `main` one at a time — only the claimed files
|
|
|
34
34
|
generated files rebuilt, your checks run — and cleans up after each. Like a
|
|
35
35
|
canal lock-keeper: one boat through at a time, the water levels kept safe.
|
|
36
36
|
|
|
37
|
-
> **Status: 0.0.
|
|
37
|
+
> **Status: 0.0.2, pre-alpha.** On PyPI since 0.0.1 (2026-11-08). Claims, the
|
|
38
38
|
> queue, landing, generated files, structural merges, the kit's hooks and
|
|
39
39
|
> `install` are built and tested on scratch repositories; commands and files
|
|
40
40
|
> may still change before 0.1.0. The design of
|
|
@@ -44,7 +44,7 @@ canal lock-keeper: one boat through at a time, the water levels kept safe.
|
|
|
44
44
|
## Install
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
|
|
47
|
+
uv tool install lockmaster # or pipx / pip; Python ≥ 3.9, no dependencies
|
|
48
48
|
lockmaster install . # the work-system kit into this repository, pinned
|
|
49
49
|
```
|
|
50
50
|
|
|
@@ -10,7 +10,7 @@ lockmaster puts the changes onto `main` one at a time — only the claimed files
|
|
|
10
10
|
generated files rebuilt, your checks run — and cleans up after each. Like a
|
|
11
11
|
canal lock-keeper: one boat through at a time, the water levels kept safe.
|
|
12
12
|
|
|
13
|
-
> **Status: 0.0.
|
|
13
|
+
> **Status: 0.0.2, pre-alpha.** On PyPI since 0.0.1 (2026-11-08). Claims, the
|
|
14
14
|
> queue, landing, generated files, structural merges, the kit's hooks and
|
|
15
15
|
> `install` are built and tested on scratch repositories; commands and files
|
|
16
16
|
> may still change before 0.1.0. The design of
|
|
@@ -20,7 +20,7 @@ canal lock-keeper: one boat through at a time, the water levels kept safe.
|
|
|
20
20
|
## Install
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
|
|
23
|
+
uv tool install lockmaster # or pipx / pip; Python ≥ 3.9, no dependencies
|
|
24
24
|
lockmaster install . # the work-system kit into this repository, pinned
|
|
25
25
|
```
|
|
26
26
|
|
|
@@ -17,14 +17,18 @@ Claude Code does.
|
|
|
17
17
|
background (the session is going away)
|
|
18
18
|
|
|
19
19
|
lockmaster is found at `$LOCKMASTER_SRC`, in this repository's own `src/`
|
|
20
|
-
(lockmaster itself),
|
|
21
|
-
|
|
20
|
+
(lockmaster itself), as a package this interpreter imports, or in the
|
|
21
|
+
environment of the `lockmaster` command on PATH (`uv tool install lockmaster`,
|
|
22
|
+
pipx). A checkout beside the repository is never used: every other repository
|
|
23
|
+
runs the published package (SR-ARCHITECTURE decision 17, W-16). Without it the
|
|
24
|
+
hook does nothing.
|
|
22
25
|
It never fails a session: every error is swallowed and the turn ends as usual.
|
|
23
26
|
Python >= 3.9, standard library only.
|
|
24
27
|
"""
|
|
25
28
|
import hashlib
|
|
26
29
|
import json
|
|
27
30
|
import os
|
|
31
|
+
import shutil
|
|
28
32
|
import subprocess
|
|
29
33
|
import sys
|
|
30
34
|
import time
|
|
@@ -57,11 +61,23 @@ def candidates():
|
|
|
57
61
|
root = main_root(ROOT)
|
|
58
62
|
if root is not None:
|
|
59
63
|
yield root / "src"
|
|
60
|
-
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def command_env():
|
|
67
|
+
"""The site-packages holding the `lockmaster` command's own package: a `uv tool`
|
|
68
|
+
or pipx install lives in an environment this interpreter cannot import."""
|
|
69
|
+
exe = shutil.which("lockmaster")
|
|
70
|
+
if not exe:
|
|
71
|
+
return None
|
|
72
|
+
env = Path(exe).resolve().parent.parent
|
|
73
|
+
for site in sorted(env.glob("lib/python3*/site-packages")) + [env / "Lib" / "site-packages"]:
|
|
74
|
+
if (site / "lockmaster" / "land.py").is_file():
|
|
75
|
+
return str(site)
|
|
76
|
+
return None
|
|
61
77
|
|
|
62
78
|
|
|
63
79
|
def find_src():
|
|
64
|
-
"""The directory to put on sys.path, "" when lockmaster is
|
|
80
|
+
"""The directory to put on sys.path, "" when lockmaster is importable, None when absent."""
|
|
65
81
|
for c in candidates():
|
|
66
82
|
if (c / "lockmaster" / "land.py").is_file():
|
|
67
83
|
return str(c)
|
|
@@ -69,7 +85,7 @@ def find_src():
|
|
|
69
85
|
import lockmaster # noqa: F401
|
|
70
86
|
return ""
|
|
71
87
|
except ImportError:
|
|
72
|
-
return
|
|
88
|
+
return command_env()
|
|
73
89
|
|
|
74
90
|
|
|
75
91
|
def load():
|
|
@@ -84,7 +100,9 @@ def load():
|
|
|
84
100
|
|
|
85
101
|
|
|
86
102
|
def command(src):
|
|
87
|
-
|
|
103
|
+
if not src or src == command_env():
|
|
104
|
+
return "lockmaster"
|
|
105
|
+
return "PYTHONPATH={} python3 -m lockmaster".format(src)
|
|
88
106
|
|
|
89
107
|
|
|
90
108
|
def detach(src, args, cwd):
|
|
@@ -89,13 +89,31 @@ def read_now():
|
|
|
89
89
|
return head.rstrip() + "\n", flight, res
|
|
90
90
|
|
|
91
91
|
|
|
92
|
+
def command_env():
|
|
93
|
+
"""The site-packages of the `lockmaster` command on PATH (`uv tool`, pipx)."""
|
|
94
|
+
import shutil
|
|
95
|
+
exe = shutil.which("lockmaster")
|
|
96
|
+
if not exe:
|
|
97
|
+
return None
|
|
98
|
+
env = Path(exe).resolve().parent.parent
|
|
99
|
+
for site in sorted(env.glob("lib/python3*/site-packages")) + [env / "Lib" / "site-packages"]:
|
|
100
|
+
if (site / "lockmaster" / "now.py").is_file():
|
|
101
|
+
return site
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
|
|
92
105
|
def renderer():
|
|
93
|
-
"""lockmaster's NOW.md renderer (`lockmaster.now`) when it is
|
|
94
|
-
|
|
95
|
-
`lockmaster status --now`
|
|
96
|
-
|
|
106
|
+
"""lockmaster's NOW.md renderer (`lockmaster.now`) when it is reachable —
|
|
107
|
+
this repository's own src/, importable, or the `lockmaster` command's
|
|
108
|
+
environment — so NOW.md always equals `lockmaster status --now`
|
|
109
|
+
(SR-ARCHITECTURE decision 14, W-07)."""
|
|
110
|
+
for extra in (None, ROOT / "src", "command"):
|
|
111
|
+
if extra == "command":
|
|
112
|
+
extra = command_env()
|
|
113
|
+
if extra is None:
|
|
114
|
+
continue
|
|
97
115
|
if extra is not None:
|
|
98
|
-
if not (extra / "lockmaster" / "now.py").is_file():
|
|
116
|
+
if not (Path(extra) / "lockmaster" / "now.py").is_file():
|
|
99
117
|
continue
|
|
100
118
|
sys.path.insert(0, str(extra))
|
|
101
119
|
try:
|
|
@@ -11,9 +11,11 @@ a session that can. **Do not ask the decider about commits, lock files,
|
|
|
11
11
|
worktrees, `index.lock` or "Operation not permitted". Do not try another git
|
|
12
12
|
command to get around a refusal.**
|
|
13
13
|
|
|
14
|
-
`lockmaster` below
|
|
15
|
-
`
|
|
16
|
-
repository
|
|
14
|
+
`lockmaster` below is the published package's command: `uv tool install
|
|
15
|
+
lockmaster` (or `pipx install lockmaster`) puts it on the PATH once per
|
|
16
|
+
machine. A lockmaster checkout beside this repository is never used — this
|
|
17
|
+
repository runs the release on PyPI. Not on the PATH → say so in one line
|
|
18
|
+
and stop; do not run lockmaster from a checkout.
|
|
17
19
|
|
|
18
20
|
## Cowork, or any session editing main's checkout directly
|
|
19
21
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lockmaster
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
4
4
|
Summary: Lands AI-agent changes into main one at a time, without ever losing work.
|
|
5
5
|
Author: Arpit Arya
|
|
6
6
|
License-Expression: MIT
|
|
@@ -34,7 +34,7 @@ lockmaster puts the changes onto `main` one at a time — only the claimed files
|
|
|
34
34
|
generated files rebuilt, your checks run — and cleans up after each. Like a
|
|
35
35
|
canal lock-keeper: one boat through at a time, the water levels kept safe.
|
|
36
36
|
|
|
37
|
-
> **Status: 0.0.
|
|
37
|
+
> **Status: 0.0.2, pre-alpha.** On PyPI since 0.0.1 (2026-11-08). Claims, the
|
|
38
38
|
> queue, landing, generated files, structural merges, the kit's hooks and
|
|
39
39
|
> `install` are built and tested on scratch repositories; commands and files
|
|
40
40
|
> may still change before 0.1.0. The design of
|
|
@@ -44,7 +44,7 @@ canal lock-keeper: one boat through at a time, the water levels kept safe.
|
|
|
44
44
|
## Install
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
|
|
47
|
+
uv tool install lockmaster # or pipx / pip; Python ≥ 3.9, no dependencies
|
|
48
48
|
lockmaster install . # the work-system kit into this repository, pinned
|
|
49
49
|
```
|
|
50
50
|
|
|
@@ -262,3 +262,51 @@ class KitNamesNoRepositoryRule(unittest.TestCase):
|
|
|
262
262
|
|
|
263
263
|
if __name__ == "__main__":
|
|
264
264
|
unittest.main()
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class HooksFindThePublishedPackage(Installed):
|
|
268
|
+
"""W-16: a repository runs the published package. The hooks find lockmaster in
|
|
269
|
+
the `lockmaster` command's own environment (`uv tool`, pipx) and never in a
|
|
270
|
+
checkout beside the repository."""
|
|
271
|
+
|
|
272
|
+
PROBE = ("import importlib.util, sys\n"
|
|
273
|
+
"s = importlib.util.spec_from_file_location('h', sys.argv[1])\n"
|
|
274
|
+
"m = importlib.util.module_from_spec(s); s.loader.exec_module(m)\n"
|
|
275
|
+
"print(getattr(m, sys.argv[2])())\n")
|
|
276
|
+
|
|
277
|
+
def probe(self, hook, fn, path):
|
|
278
|
+
env = {k: v for k, v in os.environ.items() if k not in ("LOCKMASTER_SRC", "PYTHONPATH")}
|
|
279
|
+
env["PATH"] = path
|
|
280
|
+
r = subprocess.run([sys.executable, "-I", "-c", self.PROBE, str(self.root / ".claude" / "hooks" / hook), fn],
|
|
281
|
+
capture_output=True, text=True, env=env, cwd=str(self.root))
|
|
282
|
+
self.assertEqual(r.returncode, 0, r.stderr)
|
|
283
|
+
return r.stdout.strip()
|
|
284
|
+
|
|
285
|
+
def tool_env(self):
|
|
286
|
+
env = self.root.parent / "tools" / "lockmaster"
|
|
287
|
+
site = env / "lib" / "python3.13" / "site-packages"
|
|
288
|
+
shutil.copytree(str(ROOT_SRC / "lockmaster"), str(site / "lockmaster"))
|
|
289
|
+
(env / "bin").mkdir(parents=True)
|
|
290
|
+
exe = env / "bin" / "lockmaster"
|
|
291
|
+
exe.write_text("#!/bin/sh\n")
|
|
292
|
+
exe.chmod(0o755)
|
|
293
|
+
return env / "bin", site
|
|
294
|
+
|
|
295
|
+
def test_auto_land_finds_the_commands_environment(self):
|
|
296
|
+
bindir, site = self.tool_env()
|
|
297
|
+
path = os.pathsep.join([str(bindir), os.path.dirname(shutil.which("git"))])
|
|
298
|
+
self.assertEqual(self.probe("auto-land.py", "find_src", path), str(site.resolve()))
|
|
299
|
+
|
|
300
|
+
def test_now_ledger_finds_the_commands_environment(self):
|
|
301
|
+
bindir, _ = self.tool_env()
|
|
302
|
+
path = os.pathsep.join([str(bindir), os.path.dirname(shutil.which("git"))])
|
|
303
|
+
self.assertNotEqual(self.probe("now-ledger.py", "renderer", path), "None")
|
|
304
|
+
|
|
305
|
+
def test_a_checkout_beside_the_repository_is_never_used(self):
|
|
306
|
+
beside = self.root.parent / "lockmaster" / "src"
|
|
307
|
+
shutil.copytree(str(ROOT_SRC / "lockmaster"), str(beside / "lockmaster"))
|
|
308
|
+
path = os.path.dirname(shutil.which("git"))
|
|
309
|
+
self.assertEqual(self.probe("auto-land.py", "find_src", path), "None")
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
from support import SRC as ROOT_SRC # noqa: E402
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|