methodproof 0.8.7__tar.gz → 0.8.8__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.
- {methodproof-0.8.7 → methodproof-0.8.8}/CHANGELOG.md +5 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/PKG-INFO +1 -1
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/agents/watcher.py +33 -7
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/repos.py +2 -1
- {methodproof-0.8.7 → methodproof-0.8.8}/pyproject.toml +1 -1
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_repos.py +18 -0
- methodproof-0.8.8/tests/test_watcher_git.py +41 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/.github/workflows/ci.yml +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/.gitignore +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/LICENSE +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/README.md +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/__init__.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/__main__.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/_daemon.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/agents/__init__.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/agents/base.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/agents/music.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/agents/terminal.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/analysis.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/binding.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/bip39.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/bridge.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/cli.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/config.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/crypto.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/e2e.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/graph.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/hook.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/hooks/__init__.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/hooks/claude_code.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/hooks/claude_code.sh +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/hooks/cline_hook.sh +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/hooks/codex_hook.sh +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/hooks/gemini_hook.sh +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/hooks/install.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/hooks/kiro_hook.sh +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/hooks/mcp_register.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/hooks/model_cache.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/hooks/openclaw/HOOK.md +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/hooks/openclaw/handler.ts +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/hooks/openclaw_install.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/hooks/opencode_plugin.js +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/hooks/wrappers.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/integrity.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/kdf.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/keychain.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/live.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/lock.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/mcp.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/migrate_db.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/proxy.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/proxy_daemon.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/skills/methodproof/SKILL.md +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/store.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/sync.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/tui/__init__.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/tui/consent.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/tui/init.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/tui/log.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/tui/login_success.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/tui/review.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/tui/start.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/tui/status.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/tui/theme.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/viewer.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/methodproof/wordlist.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/test_windows_compat.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/__init__.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/conftest.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_analysis.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_cli_auth.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_cli_config.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_cli_helpers.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_cli_session.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_cli_share.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_cli_start.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_cli_update.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_e2e_integration.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_graph.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_hooks.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_live.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_model_cache.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_openclaw_hooks.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_profiles.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_security.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_store.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_sync.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_viewer.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_watcher_ignore.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/tests/test_wrappers.py +0 -0
- {methodproof-0.8.7 → methodproof-0.8.8}/uv.lock +0 -0
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.8] — 2026-05-30
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **Git telemetry inside worktrees** — the watcher and sub-repo enumeration both assumed `.git` is a directory, but in a `git worktree` it is a file pointing at the shared git dir. As a result, running `mp start` from a worktree captured zero `git_commit`/`git_branch_switch` events (the poller raised `NotADirectoryError` reading `.git/refs` and `.git/HEAD`, swallowed silently) and `mp push`/`mp resync` skipped the worktree's repo entirely. The poller now resolves the per-worktree git dir (HEAD) and shared common dir (refs) via `git rev-parse --absolute-git-dir --git-common-dir` (`methodproof/agents/watcher.py`), and sub-repo enumeration detects worktrees via `os.path.exists` rather than `os.path.isdir` (`methodproof/repos.py`).
|
|
7
|
+
|
|
3
8
|
## [0.8.7] — 2026-05-15
|
|
4
9
|
|
|
5
10
|
### Fixed
|
|
@@ -268,13 +268,39 @@ def _read_branch(head_file: Path) -> str:
|
|
|
268
268
|
return ""
|
|
269
269
|
|
|
270
270
|
|
|
271
|
+
def _resolve_git_dirs(watch_dir: str) -> tuple[Path, Path] | None:
|
|
272
|
+
"""Resolve (git_dir, common_dir) for `watch_dir`, or None if not a git repo.
|
|
273
|
+
|
|
274
|
+
HEAD lives in git_dir; shared refs live in common_dir. The two diverge
|
|
275
|
+
inside a git worktree, where `.git` is a file rather than a directory.
|
|
276
|
+
"""
|
|
277
|
+
try:
|
|
278
|
+
out = subprocess.run(
|
|
279
|
+
["git", "-C", watch_dir, "rev-parse", "--absolute-git-dir", "--git-common-dir"],
|
|
280
|
+
capture_output=True, text=True, timeout=5,
|
|
281
|
+
)
|
|
282
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
283
|
+
return None
|
|
284
|
+
if out.returncode != 0:
|
|
285
|
+
return None
|
|
286
|
+
lines = out.stdout.splitlines()
|
|
287
|
+
if len(lines) < 2:
|
|
288
|
+
return None
|
|
289
|
+
git_dir = Path(lines[0].strip())
|
|
290
|
+
common_dir = Path(lines[1].strip())
|
|
291
|
+
if not common_dir.is_absolute():
|
|
292
|
+
common_dir = (Path(watch_dir) / common_dir).resolve()
|
|
293
|
+
return git_dir, common_dir
|
|
294
|
+
|
|
295
|
+
|
|
271
296
|
def _poll_git(watch_dir: str, stop: threading.Event) -> None:
|
|
272
|
-
"""Poll
|
|
273
|
-
|
|
274
|
-
if
|
|
297
|
+
"""Poll shared refs for new commits and HEAD for branch switches."""
|
|
298
|
+
dirs = _resolve_git_dirs(watch_dir)
|
|
299
|
+
if dirs is None:
|
|
275
300
|
return
|
|
301
|
+
git_dir, common_dir = dirs
|
|
276
302
|
seen: set[str] = set()
|
|
277
|
-
refs =
|
|
303
|
+
refs = common_dir / "refs" / "heads"
|
|
278
304
|
head_file = git_dir / "HEAD"
|
|
279
305
|
last_branch = _read_branch(head_file)
|
|
280
306
|
while not stop.is_set():
|
|
@@ -284,7 +310,7 @@ def _poll_git(watch_dir: str, stop: threading.Event) -> None:
|
|
|
284
310
|
if sha not in seen:
|
|
285
311
|
seen.add(sha)
|
|
286
312
|
if len(seen) > 1:
|
|
287
|
-
_log_commit(watch_dir, sha)
|
|
313
|
+
_log_commit(watch_dir, sha, head_file)
|
|
288
314
|
except OSError:
|
|
289
315
|
pass
|
|
290
316
|
current_branch = _read_branch(head_file)
|
|
@@ -296,7 +322,7 @@ def _poll_git(watch_dir: str, stop: threading.Event) -> None:
|
|
|
296
322
|
stop.wait(2)
|
|
297
323
|
|
|
298
324
|
|
|
299
|
-
def _log_commit(watch_dir: str, sha: str) -> None:
|
|
325
|
+
def _log_commit(watch_dir: str, sha: str, head_file: Path) -> None:
|
|
300
326
|
try:
|
|
301
327
|
fmt = subprocess.run(
|
|
302
328
|
["git", "-C", watch_dir, "log", "-1",
|
|
@@ -336,7 +362,7 @@ def _log_commit(watch_dir: str, sha: str) -> None:
|
|
|
336
362
|
meta["body"] = body
|
|
337
363
|
if file_statuses:
|
|
338
364
|
meta["file_statuses"] = file_statuses
|
|
339
|
-
branch = _read_branch(
|
|
365
|
+
branch = _read_branch(head_file)
|
|
340
366
|
if branch:
|
|
341
367
|
meta["branch"] = branch
|
|
342
368
|
include_lines = base.is_content_captured()
|
|
@@ -26,7 +26,8 @@ def enumerate_sub_repos(watch_dir: str, max_depth: int = 2) -> list[dict[str, st
|
|
|
26
26
|
def visit(path: str, depth: int) -> None:
|
|
27
27
|
if not os.path.isdir(path):
|
|
28
28
|
return
|
|
29
|
-
|
|
29
|
+
# `.git` is a directory in a normal clone, a file in a worktree.
|
|
30
|
+
if os.path.exists(os.path.join(path, ".git")):
|
|
30
31
|
url = _remote_url(path)
|
|
31
32
|
if url and url not in seen_urls:
|
|
32
33
|
rel = os.path.relpath(path, watch_dir)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "methodproof"
|
|
3
|
-
version = "0.8.
|
|
3
|
+
version = "0.8.8"
|
|
4
4
|
description = "See how you code. Capture and visualize your engineering process."
|
|
5
5
|
requires-python = ">=3.12"
|
|
6
6
|
dependencies = ["watchdog>=4.0", "websocket-client>=1.7", "cryptography>=46.0.7", "keyring>=25.0", "textual>=0.59", "rich>=13.7", "sqlcipher3>=0.6"]
|
|
@@ -12,6 +12,9 @@ from methodproof import repos
|
|
|
12
12
|
def _git_init(path: Path, remote: str) -> None:
|
|
13
13
|
path.mkdir(parents=True, exist_ok=True)
|
|
14
14
|
subprocess.run(["git", "init", "-q"], cwd=path, check=True)
|
|
15
|
+
# CI runners have no global git identity; commits need a local one.
|
|
16
|
+
subprocess.run(["git", "config", "user.email", "test@methodproof.com"], cwd=path, check=True)
|
|
17
|
+
subprocess.run(["git", "config", "user.name", "test"], cwd=path, check=True)
|
|
15
18
|
subprocess.run(["git", "remote", "add", "origin", remote], cwd=path, check=True)
|
|
16
19
|
|
|
17
20
|
|
|
@@ -71,3 +74,18 @@ def test_enumerate_sub_repos_skips_non_repo_dirs(tmp_path: Path) -> None:
|
|
|
71
74
|
assert "node_modules" not in rels
|
|
72
75
|
assert "not-a-repo" not in rels
|
|
73
76
|
assert "real" in rels
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_enumerate_sub_repos_detects_worktree(tmp_path: Path) -> None:
|
|
80
|
+
main = tmp_path / "main"
|
|
81
|
+
_git_init(main, "https://github.com/me/wt")
|
|
82
|
+
subprocess.run(["git", "commit", "-q", "--allow-empty", "-m", "init"],
|
|
83
|
+
cwd=main, check=True)
|
|
84
|
+
wt = tmp_path / "wt-feature"
|
|
85
|
+
subprocess.run(["git", "worktree", "add", "-q", str(wt), "-b", "feature"],
|
|
86
|
+
cwd=main, check=True)
|
|
87
|
+
|
|
88
|
+
# `.git` is a file in the worktree, not a directory.
|
|
89
|
+
assert (wt / ".git").is_file()
|
|
90
|
+
found = repos.enumerate_sub_repos(str(wt), max_depth=1)
|
|
91
|
+
assert found == [{"remote_url": "https://github.com/me/wt", "rel_path": ""}]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Tests for git dir resolution — normal clones vs worktrees."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from methodproof.agents import watcher
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _git_init(path: Path) -> None:
|
|
10
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
11
|
+
subprocess.run(["git", "init", "-q"], cwd=path, check=True)
|
|
12
|
+
# CI runners have no global git identity; commits need a local one.
|
|
13
|
+
subprocess.run(["git", "config", "user.email", "test@methodproof.com"], cwd=path, check=True)
|
|
14
|
+
subprocess.run(["git", "config", "user.name", "test"], cwd=path, check=True)
|
|
15
|
+
subprocess.run(["git", "commit", "-q", "--allow-empty", "-m", "init"],
|
|
16
|
+
cwd=path, check=True)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_resolve_git_dirs_normal_repo(tmp_path: Path) -> None:
|
|
20
|
+
_git_init(tmp_path)
|
|
21
|
+
git_dir, common_dir = watcher._resolve_git_dirs(str(tmp_path))
|
|
22
|
+
assert git_dir == common_dir
|
|
23
|
+
assert (git_dir / "HEAD").is_file()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_resolve_git_dirs_worktree(tmp_path: Path) -> None:
|
|
27
|
+
main = tmp_path / "main"
|
|
28
|
+
_git_init(main)
|
|
29
|
+
wt = tmp_path / "wt-feature"
|
|
30
|
+
subprocess.run(["git", "worktree", "add", "-q", str(wt), "-b", "feature"],
|
|
31
|
+
cwd=main, check=True)
|
|
32
|
+
|
|
33
|
+
git_dir, common_dir = watcher._resolve_git_dirs(str(wt))
|
|
34
|
+
# Worktree HEAD is per-worktree; shared refs live in the common dir.
|
|
35
|
+
assert git_dir != common_dir
|
|
36
|
+
assert watcher._read_branch(git_dir / "HEAD") == "feature"
|
|
37
|
+
assert (common_dir / "refs" / "heads" / "feature").is_file()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_resolve_git_dirs_non_repo(tmp_path: Path) -> None:
|
|
41
|
+
assert watcher._resolve_git_dirs(str(tmp_path)) is None
|
|
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
|
|
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
|