methodproof 0.7.26__tar.gz → 0.7.28__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 (86) hide show
  1. {methodproof-0.7.26 → methodproof-0.7.28}/CHANGELOG.md +17 -0
  2. {methodproof-0.7.26 → methodproof-0.7.28}/PKG-INFO +1 -1
  3. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/agents/terminal.py +18 -16
  4. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/agents/watcher.py +23 -6
  5. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/config.py +3 -0
  6. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/hook.py +8 -5
  7. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/hooks/claude_code.py +23 -1
  8. {methodproof-0.7.26 → methodproof-0.7.28}/pyproject.toml +1 -1
  9. {methodproof-0.7.26 → methodproof-0.7.28}/.github/workflows/ci.yml +0 -0
  10. {methodproof-0.7.26 → methodproof-0.7.28}/.gitignore +0 -0
  11. {methodproof-0.7.26 → methodproof-0.7.28}/LICENSE +0 -0
  12. {methodproof-0.7.26 → methodproof-0.7.28}/README.md +0 -0
  13. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/__init__.py +0 -0
  14. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/__main__.py +0 -0
  15. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/_daemon.py +0 -0
  16. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/agents/__init__.py +0 -0
  17. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/agents/base.py +0 -0
  18. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/agents/music.py +0 -0
  19. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/analysis.py +0 -0
  20. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/binding.py +0 -0
  21. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/bip39.py +0 -0
  22. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/bridge.py +0 -0
  23. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/cli.py +0 -0
  24. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/crypto.py +0 -0
  25. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/e2e.py +0 -0
  26. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/graph.py +0 -0
  27. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/hooks/__init__.py +0 -0
  28. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/hooks/claude_code.sh +0 -0
  29. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/hooks/cline_hook.sh +0 -0
  30. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/hooks/codex_hook.sh +0 -0
  31. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/hooks/gemini_hook.sh +0 -0
  32. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/hooks/install.py +0 -0
  33. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/hooks/kiro_hook.sh +0 -0
  34. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/hooks/mcp_register.py +0 -0
  35. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/hooks/openclaw/HOOK.md +0 -0
  36. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/hooks/openclaw/handler.ts +0 -0
  37. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/hooks/openclaw_install.py +0 -0
  38. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/hooks/opencode_plugin.js +0 -0
  39. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/hooks/wrappers.py +0 -0
  40. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/integrity.py +0 -0
  41. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/kdf.py +0 -0
  42. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/keychain.py +0 -0
  43. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/live.py +0 -0
  44. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/lock.py +0 -0
  45. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/mcp.py +0 -0
  46. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/migrate_db.py +0 -0
  47. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/proxy.py +0 -0
  48. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/proxy_daemon.py +0 -0
  49. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/repos.py +0 -0
  50. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/skills/methodproof/SKILL.md +0 -0
  51. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/store.py +0 -0
  52. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/sync.py +0 -0
  53. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/tui/__init__.py +0 -0
  54. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/tui/consent.py +0 -0
  55. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/tui/init.py +0 -0
  56. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/tui/log.py +0 -0
  57. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/tui/login_success.py +0 -0
  58. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/tui/review.py +0 -0
  59. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/tui/start.py +0 -0
  60. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/tui/status.py +0 -0
  61. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/tui/theme.py +0 -0
  62. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/viewer.py +0 -0
  63. {methodproof-0.7.26 → methodproof-0.7.28}/methodproof/wordlist.py +0 -0
  64. {methodproof-0.7.26 → methodproof-0.7.28}/test_windows_compat.py +0 -0
  65. {methodproof-0.7.26 → methodproof-0.7.28}/tests/__init__.py +0 -0
  66. {methodproof-0.7.26 → methodproof-0.7.28}/tests/conftest.py +0 -0
  67. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_analysis.py +0 -0
  68. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_cli_auth.py +0 -0
  69. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_cli_config.py +0 -0
  70. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_cli_helpers.py +0 -0
  71. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_cli_session.py +0 -0
  72. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_cli_share.py +0 -0
  73. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_cli_start.py +0 -0
  74. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_cli_update.py +0 -0
  75. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_e2e_integration.py +0 -0
  76. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_graph.py +0 -0
  77. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_hooks.py +0 -0
  78. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_live.py +0 -0
  79. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_openclaw_hooks.py +0 -0
  80. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_profiles.py +0 -0
  81. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_security.py +0 -0
  82. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_store.py +0 -0
  83. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_sync.py +0 -0
  84. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_viewer.py +0 -0
  85. {methodproof-0.7.26 → methodproof-0.7.28}/tests/test_wrappers.py +0 -0
  86. {methodproof-0.7.26 → methodproof-0.7.28}/uv.lock +0 -0
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.28] — 2026-04-12
4
+
5
+ ### Added
6
+ - **`cwd` in terminal events** — shell hook (bash/zsh/PowerShell) now writes working directory to `commands.jsonl`; terminal agent reads it into `terminal_cmd` metadata. Requires re-running `mp init` to update the installed hook.
7
+ - **Git commit enrichment** — `git_commit` events now include `author`, `author_email`, `committed_at` (ISO 8601), `parent_hash`, and `body` (full commit message body when it differs from subject). Single `git log` call instead of multiple.
8
+ - **`language` on `file_delete`** — was always empty string; now extracted from file extension same as `file_create`.
9
+ - **`skipped` count in `test_run`** — pytest, jest, and go test output now parsed for skipped test count.
10
+
11
+ ### Fixed
12
+ - **`agent_complete.last_message_preview` not journal-gated** — preview of agent's final message leaked content when journal mode was off. Now stripped with other content fields.
13
+ - **`git_commit.body` not journal-gated** — full commit body (multi-line messages) added to journal gate.
14
+
15
+ ## [0.7.27] — 2026-04-12
16
+
17
+ ### Fixed
18
+ - **`result_preview` was Python repr for structured tool responses** — `str([{"type": "text", "text": "..."}])` produced unreadable output for Read/Grep/Glob tools. `_extract_result_text` now handles all three shapes Claude Code sends: plain string (Bash/Write/Edit), content block list (Read/Grep/Glob), and dict.
19
+
3
20
  ## [0.7.26] — 2026-04-12
4
21
 
5
22
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: methodproof
3
- Version: 0.7.26
3
+ Version: 0.7.28
4
4
  Summary: See how you code. Capture and visualize your engineering process.
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -22,8 +22,8 @@ TEST_FRAMEWORKS = {
22
22
  "cargo_test": re.compile(r"\bcargo test\b"),
23
23
  }
24
24
 
25
- _PYTEST_RE = re.compile(r"(\d+) passed(?:.*?(\d+) failed)?")
26
- _JEST_RE = re.compile(r"Tests:\s+(?:(\d+) failed,\s+)?(\d+) passed")
25
+ _PYTEST_RE = re.compile(r"(\d+) passed(?:.*?(\d+) failed)?(?:.*?(\d+) skipped)?")
26
+ _JEST_RE = re.compile(r"Tests:\s+(?:(\d+) failed,\s+)?(\d+) passed(?:,\s+(\d+) skipped)?")
27
27
  _CARGO_RE = re.compile(r"(\d+) passed.*?(\d+) failed")
28
28
 
29
29
 
@@ -34,26 +34,25 @@ def _detect_test(command: str) -> str | None:
34
34
  return None
35
35
 
36
36
 
37
- def _parse_test_results(output: str, framework: str, exit_code: int) -> tuple[int, int]:
38
- """Extract pass/fail counts from test output. Falls back to exit code heuristic."""
37
+ def _parse_test_results(output: str, framework: str, exit_code: int) -> tuple[int, int, int]:
38
+ """Extract pass/fail/skipped counts from test output. Falls back to exit code heuristic."""
39
39
  if framework == "pytest":
40
40
  m = _PYTEST_RE.search(output)
41
41
  if m:
42
- return int(m.group(1)), int(m.group(2) or 0)
42
+ return int(m.group(1)), int(m.group(2) or 0), int(m.group(3) or 0)
43
43
  elif framework == "jest":
44
44
  m = _JEST_RE.search(output)
45
45
  if m:
46
- return int(m.group(2) or 0), int(m.group(1) or 0)
46
+ return int(m.group(2) or 0), int(m.group(1) or 0), int(m.group(3) or 0)
47
47
  elif framework == "go_test":
48
- return output.count("--- PASS"), output.count("--- FAIL")
48
+ return output.count("--- PASS"), output.count("--- FAIL"), output.count("--- SKIP")
49
49
  elif framework == "cargo_test":
50
50
  m = _CARGO_RE.search(output)
51
51
  if m:
52
- return int(m.group(1)), int(m.group(2))
53
- # Fallback: exit code 0 = all passed, non-zero = at least 1 failure
52
+ return int(m.group(1)), int(m.group(2)), 0
54
53
  if exit_code == 0:
55
- return 1, 0
56
- return 0, 1
54
+ return 1, 0, 0
55
+ return 0, 1, 0
57
56
 
58
57
 
59
58
  def start(stop: threading.Event) -> None:
@@ -93,19 +92,22 @@ def _process(line: str) -> None:
93
92
  exit_code = entry.get("exit_code", 0)
94
93
  duration = entry.get("duration_ms", 0)
95
94
  output = entry.get("output", "")[:500]
96
- # Redact output if it contains secrets
97
95
  if SENSITIVE.search(output):
98
96
  output = "[redacted — contains sensitive content]"
97
+ cwd = entry.get("cwd", "")
99
98
 
100
- base.emit("terminal_cmd", {
99
+ meta: dict = {
101
100
  "command": command, "exit_code": exit_code,
102
101
  "output_snippet": output, "duration_ms": duration,
103
- })
102
+ }
103
+ if cwd:
104
+ meta["cwd"] = cwd
105
+ base.emit("terminal_cmd", meta)
104
106
 
105
107
  framework = _detect_test(command)
106
108
  if framework:
107
- passed, failed = _parse_test_results(output, framework, exit_code)
109
+ passed, failed, skipped = _parse_test_results(output, framework, exit_code)
108
110
  base.emit("test_run", {
109
111
  "framework": framework, "passed": passed, "failed": failed,
110
- "duration_ms": duration,
112
+ "skipped": skipped, "duration_ms": duration,
111
113
  })
@@ -139,7 +139,8 @@ class _Handler(FileSystemEventHandler):
139
139
  return
140
140
  path = self._relpath(event.src_path)
141
141
  self._hashes.pop(path, None)
142
- base.emit("file_delete", {"path": path})
142
+ lang = Path(event.src_path).suffix.lstrip(".")
143
+ base.emit("file_delete", {"path": path, "language": lang})
143
144
 
144
145
 
145
146
  def _poll_git(watch_dir: str, stop: threading.Event) -> None:
@@ -164,17 +165,33 @@ def _poll_git(watch_dir: str, stop: threading.Event) -> None:
164
165
 
165
166
  def _log_commit(watch_dir: str, sha: str) -> None:
166
167
  try:
167
- msg = subprocess.run(
168
- ["git", "-C", watch_dir, "log", "-1", "--format=%s", sha],
168
+ # Single git call: subject\x00author\x00email\x00iso_date\x00parent_hash\x00full_body
169
+ fmt = subprocess.run(
170
+ ["git", "-C", watch_dir, "log", "-1",
171
+ "--format=%s%x00%an%x00%ae%x00%ai%x00%P%x00%B", sha],
169
172
  capture_output=True, text=True, timeout=5,
170
- ).stdout.strip()
173
+ ).stdout
174
+ parts = fmt.split("\x00", 5)
175
+ subject = parts[0].strip() if len(parts) > 0 else ""
176
+ author = parts[1].strip() if len(parts) > 1 else ""
177
+ author_email = parts[2].strip() if len(parts) > 2 else ""
178
+ committed_at = parts[3].strip() if len(parts) > 3 else ""
179
+ parent_hash = parts[4].strip()[:7] if len(parts) > 4 else ""
180
+ body = parts[5].strip() if len(parts) > 5 else ""
171
181
  files = subprocess.run(
172
182
  ["git", "-C", watch_dir, "diff-tree", "--no-commit-id", "-r", "--name-only", sha],
173
183
  capture_output=True, text=True, timeout=5,
174
184
  ).stdout.strip().splitlines()
175
185
  except Exception:
176
- msg, files = "", []
177
- meta: dict[str, object] = {"hash": sha[:7], "message": msg, "files_changed": files}
186
+ subject, author, author_email, committed_at, parent_hash, body, files = "", "", "", "", "", "", []
187
+ meta: dict[str, object] = {
188
+ "hash": sha[:7], "message": subject, "files_changed": files,
189
+ "author": author, "author_email": author_email, "committed_at": committed_at,
190
+ }
191
+ if parent_hash:
192
+ meta["parent_hash"] = parent_hash
193
+ if body and body != subject:
194
+ meta["body"] = body[:2000]
178
195
  diff = _git_show_diff(watch_dir, sha)
179
196
  if diff:
180
197
  meta["diff"] = diff
@@ -118,6 +118,9 @@ JOURNAL_CONTENT_FIELDS: list[tuple[str, str]] = [
118
118
  ("user_prompt", "prompt_text"),
119
119
  ("tool_call", "tool_input_preview"),
120
120
  ("tool_result", "result_preview"),
121
+ # Agent final message and commit body reveal content
122
+ ("agent_complete", "last_message_preview"),
123
+ ("git_commit", "body"),
121
124
  ]
122
125
 
123
126
 
@@ -10,10 +10,11 @@ _BASH = '''
10
10
  # methodproof-hook
11
11
  _mp_pre() { _MP_CMD="$BASH_COMMAND"; _MP_T=$SECONDS; }
12
12
  _mp_post() {
13
- local ec=$? cmd="$_MP_CMD" dur=$(( (SECONDS - ${_MP_T:-$SECONDS}) * 1000 ))
13
+ local ec=$? cmd="$_MP_CMD" dur=$(( (SECONDS - ${_MP_T:-$SECONDS}) * 1000 )) cwd
14
14
  [ -z "$cmd" ] && return
15
15
  cmd="${cmd//\\\\/\\\\\\\\}"; cmd="${cmd//\\"/\\\\\\"}"
16
- echo "{\\"command\\":\\"$cmd\\",\\"exit_code\\":$ec,\\"duration_ms\\":$dur}" >> ~/.methodproof/commands.jsonl
16
+ cwd="$(pwd)"; cwd="${cwd//\\\\/\\\\\\\\}"; cwd="${cwd//\\"/\\\\\\"}"
17
+ echo "{\\"command\\":\\"$cmd\\",\\"exit_code\\":$ec,\\"duration_ms\\":$dur,\\"cwd\\":\\"$cwd\\"}" >> ~/.methodproof/commands.jsonl
17
18
  unset _MP_CMD
18
19
  }
19
20
  trap '_mp_pre' DEBUG
@@ -24,10 +25,11 @@ _ZSH = '''
24
25
  # methodproof-hook
25
26
  _mp_pre() { _MP_CMD="$1"; _MP_T=$SECONDS; }
26
27
  _mp_post() {
27
- local ec=$? cmd="$_MP_CMD" dur=$(( (SECONDS - ${_MP_T:-$SECONDS}) * 1000 ))
28
+ local ec=$? cmd="$_MP_CMD" dur=$(( (SECONDS - ${_MP_T:-$SECONDS}) * 1000 )) cwd
28
29
  [[ -z "$cmd" ]] && return
29
30
  cmd="${cmd//\\\\/\\\\\\\\}"; cmd="${cmd//\\"/\\\\\\"}"
30
- echo "{\\"command\\":\\"$cmd\\",\\"exit_code\\":$ec,\\"duration_ms\\":$dur}" >> ~/.methodproof/commands.jsonl
31
+ cwd="$(pwd)"; cwd="${cwd//\\\\/\\\\\\\\}"; cwd="${cwd//\\"/\\\\\\"}"
32
+ echo "{\\"command\\":\\"$cmd\\",\\"exit_code\\":$ec,\\"duration_ms\\":$dur,\\"cwd\\":\\"$cwd\\"}" >> ~/.methodproof/commands.jsonl
31
33
  unset _MP_CMD
32
34
  }
33
35
  autoload -Uz add-zsh-hook
@@ -52,7 +54,8 @@ function prompt {
52
54
  if ($global:_mpCmd) {
53
55
  $dur = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() - $global:_mpT
54
56
  $cmd = $global:_mpCmd -replace '\\\\','\\\\\\\\' -replace '"','\\"'
55
- $line = "{`"command`":`"$cmd`",`"exit_code`":$ec,`"duration_ms`":$dur}"
57
+ $cwd = (Get-Location).Path -replace '\\\\','\\\\\\\\' -replace '"','\\"'
58
+ $line = "{`"command`":`"$cmd`",`"exit_code`":$ec,`"duration_ms`":$dur,`"cwd`":`"$cwd`"}"
56
59
  $logPath = Join-Path $HOME ".methodproof" "commands.jsonl"
57
60
  Add-Content -Path $logPath -Value $line
58
61
  $global:_mpCmd = $null
@@ -16,6 +16,28 @@ except ImportError:
16
16
  analyze_prompt = lambda _: {}
17
17
  compose_summary = lambda _: ""
18
18
 
19
+ def _extract_result_text(response) -> str:
20
+ """Extract plain text from tool_response regardless of shape.
21
+
22
+ Claude Code sends:
23
+ - str — Bash, Write, Edit, simple tools
24
+ - list — Read, Grep, Glob: [{"type": "text", "text": "..."}]
25
+ - dict — rare structured responses
26
+ """
27
+ if isinstance(response, str):
28
+ return response[:500]
29
+ if isinstance(response, list):
30
+ parts = [
31
+ block.get("text", "") if isinstance(block, dict) else str(block)
32
+ for block in response
33
+ if not isinstance(block, dict) or block.get("type") == "text"
34
+ ]
35
+ return "\n".join(parts)[:500]
36
+ if isinstance(response, dict):
37
+ return response.get("text") or response.get("content") or json.dumps(response)[:500]
38
+ return str(response)[:500]
39
+
40
+
19
41
  def _build_prompt_meta(text: str) -> dict:
20
42
  sa = analyze_prompt(text)
21
43
  sa["prompt_length"] = len(text)
@@ -81,7 +103,7 @@ _META_EXTRACTORS = {
81
103
  },
82
104
  "PostToolUse": lambda d: {
83
105
  "tool": _TOOL, "tool_name": d.get("tool_name", "unknown"), "success": True,
84
- "result_preview": str(d.get("tool_response") or "")[:500],
106
+ "result_preview": _extract_result_text(d.get("tool_response")),
85
107
  },
86
108
  "PostToolUseFailure": lambda d: {
87
109
  "tool": _TOOL, "tool_name": d.get("tool_name", "unknown"),
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "methodproof"
3
- version = "0.7.26"
3
+ version = "0.7.28"
4
4
  description = "See how you code. Capture and visualize your engineering process."
5
5
  requires-python = ">=3.11"
6
6
  dependencies = ["watchdog>=4.0", "websocket-client>=1.7", "cryptography>=43.0", "keyring>=25.0", "textual>=0.59", "rich>=13.7"]
File without changes
File without changes
File without changes
File without changes