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.
Files changed (63) hide show
  1. {lockmaster-0.0.1 → lockmaster-0.0.2}/PKG-INFO +3 -3
  2. {lockmaster-0.0.1 → lockmaster-0.0.2}/README.md +2 -2
  3. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/auto-land.py +24 -6
  4. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/now-ledger.py +23 -5
  5. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/skills/landing/SKILL.md +5 -3
  6. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/__init__.py +1 -1
  7. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster.egg-info/PKG-INFO +3 -3
  8. {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_kit_hooks.py +48 -0
  9. {lockmaster-0.0.1 → lockmaster-0.0.2}/LICENSE +0 -0
  10. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/block-main.py +0 -0
  11. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/claim-guard.py +0 -0
  12. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/disk-budget.py +0 -0
  13. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/guard-open-work-inbox.sh +0 -0
  14. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/inject-inbox.sh +0 -0
  15. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/release-locks.sh +0 -0
  16. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/require-progress.sh +0 -0
  17. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/session-lock.sh +0 -0
  18. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/stop-if-blocked.sh +0 -0
  19. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/work-files.py +0 -0
  20. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/hooks/worktree-cleanup.py +0 -0
  21. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/settings.json +0 -0
  22. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/claude/skills/docket/SKILL.md +0 -0
  23. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/work/BACKLOG.md +0 -0
  24. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/work/INTERVIEW.md +0 -0
  25. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/work/LESSONS.md +0 -0
  26. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/work/MACHINE.md +0 -0
  27. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/work/OPEN-WORK.md +0 -0
  28. {lockmaster-0.0.1 → lockmaster-0.0.2}/kit/work/WORKLOG.md +0 -0
  29. {lockmaster-0.0.1 → lockmaster-0.0.2}/pyproject.toml +0 -0
  30. {lockmaster-0.0.1 → lockmaster-0.0.2}/setup.cfg +0 -0
  31. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/__main__.py +0 -0
  32. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/cli.py +0 -0
  33. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/config.py +0 -0
  34. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/doctor.py +0 -0
  35. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/drivers.py +0 -0
  36. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/generated.py +0 -0
  37. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/git.py +0 -0
  38. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/install.py +0 -0
  39. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/land.py +0 -0
  40. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/merge.py +0 -0
  41. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/now.py +0 -0
  42. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/repo.py +0 -0
  43. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/status.py +0 -0
  44. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster/surface.py +0 -0
  45. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster.egg-info/SOURCES.txt +0 -0
  46. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster.egg-info/dependency_links.txt +0 -0
  47. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster.egg-info/entry_points.txt +0 -0
  48. {lockmaster-0.0.1 → lockmaster-0.0.2}/src/lockmaster.egg-info/top_level.txt +0 -0
  49. {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_autoland.py +0 -0
  50. {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_cli.py +0 -0
  51. {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_config.py +0 -0
  52. {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_gate_hooks.py +0 -0
  53. {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_hooks.py +0 -0
  54. {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_install.py +0 -0
  55. {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_land.py +0 -0
  56. {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_merge.py +0 -0
  57. {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_packaging.py +0 -0
  58. {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_parallel.py +0 -0
  59. {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_regen.py +0 -0
  60. {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_release.py +0 -0
  61. {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_status.py +0 -0
  62. {lockmaster-0.0.1 → lockmaster-0.0.2}/tests/test_waiting.py +0 -0
  63. {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.1
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.1, pre-alpha.** 0.0.1 holds the name on PyPI. Claims, the
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
- pip install lockmaster # Python ≥ 3.9, no dependencies
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.1, pre-alpha.** 0.0.1 holds the name on PyPI. Claims, the
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
- pip install lockmaster # Python ≥ 3.9, no dependencies
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), in a `lockmaster` checkout beside this repository, or as
21
- an installed package. Without it the hook does nothing.
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
- yield root.parent / "lockmaster" / "src"
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 installed, None when absent."""
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 None
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
- return "PYTHONPATH={} python3 -m lockmaster".format(src) if src else "lockmaster"
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 importable —
94
- installed, or this repository's own src/ — so NOW.md always equals
95
- `lockmaster status --now` (SR-ARCHITECTURE decision 14, W-07)."""
96
- for extra in (None, ROOT / "src"):
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 means `lockmaster` if it is on the PATH, else
15
- `PYTHONPATH=<the lockmaster checkout>/src python3 -m lockmaster` (beside this
16
- repository, e.g. `../lockmaster/src`).
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,3 +1,3 @@
1
1
  """lockmaster — lands AI-agent changes into main one at a time (SR-ARCHITECTURE)."""
2
2
 
3
- __version__ = "0.0.1"
3
+ __version__ = "0.0.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lockmaster
3
- Version: 0.0.1
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.1, pre-alpha.** 0.0.1 holds the name on PyPI. Claims, the
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
- pip install lockmaster # Python ≥ 3.9, no dependencies
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