methodproof 0.7.36__tar.gz → 0.7.38__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 (87) hide show
  1. {methodproof-0.7.36 → methodproof-0.7.38}/CHANGELOG.md +18 -0
  2. {methodproof-0.7.36 → methodproof-0.7.38}/PKG-INFO +6 -4
  3. {methodproof-0.7.36 → methodproof-0.7.38}/README.md +5 -3
  4. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/agents/base.py +7 -9
  5. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/agents/watcher.py +101 -0
  6. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/config.py +4 -14
  7. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/hooks/claude_code.py +6 -0
  8. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/hooks/claude_code.sh +54 -3
  9. methodproof-0.7.38/methodproof/hooks/gemini_hook.sh +123 -0
  10. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/hooks/install.py +5 -1
  11. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/tui/start.py +3 -1
  12. {methodproof-0.7.36 → methodproof-0.7.38}/pyproject.toml +1 -1
  13. methodproof-0.7.36/methodproof/hooks/gemini_hook.sh +0 -58
  14. {methodproof-0.7.36 → methodproof-0.7.38}/.github/workflows/ci.yml +0 -0
  15. {methodproof-0.7.36 → methodproof-0.7.38}/.gitignore +0 -0
  16. {methodproof-0.7.36 → methodproof-0.7.38}/LICENSE +0 -0
  17. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/__init__.py +0 -0
  18. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/__main__.py +0 -0
  19. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/_daemon.py +0 -0
  20. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/agents/__init__.py +0 -0
  21. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/agents/music.py +0 -0
  22. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/agents/terminal.py +0 -0
  23. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/analysis.py +0 -0
  24. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/binding.py +0 -0
  25. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/bip39.py +0 -0
  26. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/bridge.py +0 -0
  27. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/cli.py +0 -0
  28. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/crypto.py +0 -0
  29. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/e2e.py +0 -0
  30. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/graph.py +0 -0
  31. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/hook.py +0 -0
  32. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/hooks/__init__.py +0 -0
  33. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/hooks/cline_hook.sh +0 -0
  34. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/hooks/codex_hook.sh +0 -0
  35. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/hooks/kiro_hook.sh +0 -0
  36. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/hooks/mcp_register.py +0 -0
  37. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/hooks/openclaw/HOOK.md +0 -0
  38. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/hooks/openclaw/handler.ts +0 -0
  39. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/hooks/openclaw_install.py +0 -0
  40. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/hooks/opencode_plugin.js +0 -0
  41. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/hooks/wrappers.py +0 -0
  42. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/integrity.py +0 -0
  43. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/kdf.py +0 -0
  44. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/keychain.py +0 -0
  45. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/live.py +0 -0
  46. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/lock.py +0 -0
  47. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/mcp.py +0 -0
  48. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/migrate_db.py +0 -0
  49. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/proxy.py +0 -0
  50. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/proxy_daemon.py +0 -0
  51. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/repos.py +0 -0
  52. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/skills/methodproof/SKILL.md +0 -0
  53. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/store.py +0 -0
  54. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/sync.py +0 -0
  55. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/tui/__init__.py +0 -0
  56. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/tui/consent.py +0 -0
  57. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/tui/init.py +0 -0
  58. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/tui/log.py +0 -0
  59. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/tui/login_success.py +0 -0
  60. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/tui/review.py +0 -0
  61. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/tui/status.py +0 -0
  62. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/tui/theme.py +0 -0
  63. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/viewer.py +0 -0
  64. {methodproof-0.7.36 → methodproof-0.7.38}/methodproof/wordlist.py +0 -0
  65. {methodproof-0.7.36 → methodproof-0.7.38}/test_windows_compat.py +0 -0
  66. {methodproof-0.7.36 → methodproof-0.7.38}/tests/__init__.py +0 -0
  67. {methodproof-0.7.36 → methodproof-0.7.38}/tests/conftest.py +0 -0
  68. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_analysis.py +0 -0
  69. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_cli_auth.py +0 -0
  70. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_cli_config.py +0 -0
  71. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_cli_helpers.py +0 -0
  72. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_cli_session.py +0 -0
  73. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_cli_share.py +0 -0
  74. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_cli_start.py +0 -0
  75. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_cli_update.py +0 -0
  76. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_e2e_integration.py +0 -0
  77. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_graph.py +0 -0
  78. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_hooks.py +0 -0
  79. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_live.py +0 -0
  80. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_openclaw_hooks.py +0 -0
  81. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_profiles.py +0 -0
  82. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_security.py +0 -0
  83. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_store.py +0 -0
  84. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_sync.py +0 -0
  85. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_viewer.py +0 -0
  86. {methodproof-0.7.36 → methodproof-0.7.38}/tests/test_wrappers.py +0 -0
  87. {methodproof-0.7.36 → methodproof-0.7.38}/uv.lock +0 -0
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.38] — 2026-04-12
4
+
5
+ ### Added
6
+ - **Structural hunk capture on `file_edit` and `git_commit`** — watcher parses `@@ -old,count +new,count @@` headers from `git diff --unified=0` and `git show --format= --unified=0 <sha>` and emits them as `hunks: [{old_start, old_count, new_start, new_count}, ...]` on `file_edit` metadata and `file_hunks: {path: [hunks, ...]}` on `git_commit` metadata. Pure range data — no code content, no identifiers. Stays inside the structural-capture boundary alongside `lines_added` / `lines_removed`.
7
+ - **Journal-mode hunk content** — when `code_capture` consent is on (Pro tier, journal mode), each hunk additionally carries a `lines: ["+foo", "-bar", ...]` field with the actual diff lines for that hunk, capped at 2000 lines per event. Gated by the same consent flag that gates the raw `diff` field.
8
+ - **`base.is_content_captured()`** — public helper in `methodproof/agents/base.py` exposing the `code_capture` capture-flag check so agents can decide what content to include without reaching into private module state.
9
+
10
+ ### Why
11
+ Enables the platform's post-processing pipeline to distinguish `SUPERSEDES` (later commit replaces 100% of an earlier commit's added lines on each shared path) from `EXTENSION_TO` (anything less — the default) without ever needing the raw diff content. See `methodproof` changelog v0.6.30.
12
+
13
+ ## [0.7.37] — 2026-04-12
14
+
15
+ ### Changed
16
+ - **Full tool metadata capture** — shell hook now stores complete `tool_input` and `tool_response` objects alongside display previews. Previously only extracted preview strings; raw data was discarded. Enables upstream graph analysis, causal edge building, and artifact tracking from tool events.
17
+ - **Enriched shell hook** — `PreToolUse` captures `tool_input_preview` per tool type (Bash→command, Read/Edit/Write→file_path, Grep→pattern+path, Glob→pattern, Agent→description). `PostToolUse` captures `result_preview` (Bash→stdout/stderr, Read→line count, Grep→file+line count, Glob→file count) plus `tool_input_preview` so results show what was done.
18
+ - **Journal gating removed** — all captured metadata is now persisted regardless of journal mode. No more field stripping in `emit()`. `JOURNAL_CONTENT_FIELDS` retained as a reference for TUI enrichment display.
19
+ - **`tool_result` TUI shows input + result** — e.g. `Grep ✓ def main /src 3 files, 15 lines` instead of just `Grep ✓`.
20
+
3
21
  ## [0.7.36] — 2026-04-12
4
22
 
5
23
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: methodproof
3
- Version: 0.7.36
3
+ Version: 0.7.38
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
@@ -88,9 +88,10 @@ methodproof view # explore your session in the browser
88
88
  | `enter` | Event detail | Modal overlay with all metadata fields |
89
89
  | `m` | Quiet mode | Hide dim events (music, environment, MCP, context compaction) |
90
90
  | `p` | Pause | Pause/resume event polling |
91
- | `q` | Stop | Stop the session |
91
+ | `q` | Exit TUI | Detach daemon keeps recording. Reconnect with `mp connect` |
92
+ | `x` | End session | Stop recording with confirmation prompt |
92
93
 
93
- Active modes display as badges in the session bar: `J` (journal), `⏸` (scroll locked), `Q` (quiet), `T` (tree collapsed), filter name when not "all".
94
+ Active modes display as badges in the session bar: `J` (journal), `⏸` (scroll locked), `Q` (quiet), `T` (tree collapsed), filter name when not "all". Source tracking shows which AI tool session (`claude #1`, `codex #2`) generated each event.
94
95
 
95
96
  ## Security Architecture
96
97
 
@@ -178,8 +179,9 @@ flowchart TB
178
179
  | Command | What it does |
179
180
  |---------|-------------|
180
181
  | `init` | Interactive consent selector, install hooks, create data directory |
181
- | `start [--dir .] [--tags t1,t2] [--public] [--live] [--journal] [--e2e]` | Start recording |
182
+ | `start [--dir .] [--tags t1,t2] [--public] [--live] [--journal] [--e2e]` | Start recording (auto-connects if session already active) |
182
183
  | `stop` | Stop recording, build process graph |
184
+ | `connect [session_id]` | Attach TUI to an active session (defaults to current) |
183
185
  | `view [session_id]` | Open session graph in browser |
184
186
  | `log` | List sessions with sync status, visibility, tags |
185
187
  | `login [--api-url URL]` | Authenticate with the platform |
@@ -71,9 +71,10 @@ methodproof view # explore your session in the browser
71
71
  | `enter` | Event detail | Modal overlay with all metadata fields |
72
72
  | `m` | Quiet mode | Hide dim events (music, environment, MCP, context compaction) |
73
73
  | `p` | Pause | Pause/resume event polling |
74
- | `q` | Stop | Stop the session |
74
+ | `q` | Exit TUI | Detach daemon keeps recording. Reconnect with `mp connect` |
75
+ | `x` | End session | Stop recording with confirmation prompt |
75
76
 
76
- Active modes display as badges in the session bar: `J` (journal), `⏸` (scroll locked), `Q` (quiet), `T` (tree collapsed), filter name when not "all".
77
+ Active modes display as badges in the session bar: `J` (journal), `⏸` (scroll locked), `Q` (quiet), `T` (tree collapsed), filter name when not "all". Source tracking shows which AI tool session (`claude #1`, `codex #2`) generated each event.
77
78
 
78
79
  ## Security Architecture
79
80
 
@@ -161,8 +162,9 @@ flowchart TB
161
162
  | Command | What it does |
162
163
  |---------|-------------|
163
164
  | `init` | Interactive consent selector, install hooks, create data directory |
164
- | `start [--dir .] [--tags t1,t2] [--public] [--live] [--journal] [--e2e]` | Start recording |
165
+ | `start [--dir .] [--tags t1,t2] [--public] [--live] [--journal] [--e2e]` | Start recording (auto-connects if session already active) |
165
166
  | `stop` | Stop recording, build process graph |
167
+ | `connect [session_id]` | Attach TUI to an active session (defaults to current) |
166
168
  | `view [session_id]` | Open session graph in browser |
167
169
  | `log` | List sessions with sync status, visibility, tags |
168
170
  | `login [--api-url URL]` | Authenticate with the platform |
@@ -125,6 +125,13 @@ def log(level: str, event: str, **kw: object) -> None:
125
125
  sys.stderr.write(json.dumps(entry, default=str) + "\n")
126
126
 
127
127
 
128
+ def is_content_captured() -> bool:
129
+ """True when code_capture consent is on (journal/Pro mode). Agents use this
130
+ to decide whether to include line-level diff content alongside structural
131
+ metadata."""
132
+ return bool(_capture.get("code_capture", False))
133
+
134
+
128
135
  def emit(event_type: str, metadata: dict[str, Any]) -> None:
129
136
  if not _initialized:
130
137
  log("warning", "emit.before_init", type=event_type)
@@ -140,15 +147,6 @@ def emit(event_type: str, metadata: dict[str, Any]) -> None:
140
147
  if event_type == etype and not _capture.get(category, True):
141
148
  metadata.pop(field, None)
142
149
 
143
- # Journal mode gate — strip content fields when journal is OFF (default).
144
- # Structural equivalents (prompt_length, etc.) are always kept.
145
- # Journal ON = complete explicit record. Journal OFF = structural only.
146
- if not _journal_mode:
147
- from methodproof.config import JOURNAL_CONTENT_FIELDS
148
- for etype, field in JOURNAL_CONTENT_FIELDS:
149
- if event_type == etype and field in metadata:
150
- metadata.pop(field, None)
151
-
152
150
  entry = {
153
151
  "id": uuid.uuid4().hex,
154
152
  "session_id": _session_id,
@@ -46,6 +46,99 @@ IGNORE_PATTERNS = re.compile(
46
46
 
47
47
 
48
48
  _MAX_DIFF_BYTES = 50_000
49
+ _MAX_HUNK_LINES = 2_000
50
+
51
+ _HUNK_HEADER_RE = re.compile(r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@")
52
+ _DIFF_FILE_RE = re.compile(r"^diff --git a/(.+?) b/(.+?)$")
53
+
54
+
55
+ def _parse_hunks(diff_text: str, include_lines: bool) -> list[dict[str, object]]:
56
+ """Parse unified diff text into structured hunks.
57
+
58
+ Each hunk: {old_start, old_count, new_start, new_count, [lines]}.
59
+ Lines are only included when `include_lines` is True (journal / code_capture).
60
+ """
61
+ hunks: list[dict[str, object]] = []
62
+ current: dict[str, object] | None = None
63
+ total_lines = 0
64
+ for line in diff_text.splitlines():
65
+ header = _HUNK_HEADER_RE.match(line)
66
+ if header:
67
+ if current is not None:
68
+ hunks.append(current)
69
+ current = {
70
+ "old_start": int(header.group(1)),
71
+ "old_count": int(header.group(2) or 1),
72
+ "new_start": int(header.group(3)),
73
+ "new_count": int(header.group(4) or 1),
74
+ }
75
+ if include_lines:
76
+ current["lines"] = []
77
+ continue
78
+ if current is None or not include_lines:
79
+ continue
80
+ if not (line.startswith("+") or line.startswith("-")):
81
+ continue
82
+ if line.startswith("+++") or line.startswith("---"):
83
+ continue
84
+ if total_lines >= _MAX_HUNK_LINES:
85
+ continue
86
+ current["lines"].append(line) # type: ignore[union-attr]
87
+ total_lines += 1
88
+ if current is not None:
89
+ hunks.append(current)
90
+ return hunks
91
+
92
+
93
+ def _parse_show_hunks(
94
+ show_text: str, include_lines: bool,
95
+ ) -> dict[str, list[dict[str, object]]]:
96
+ """Split `git show --unified=0` output by file and parse hunks per file."""
97
+ file_hunks: dict[str, list[dict[str, object]]] = {}
98
+ current_path: str | None = None
99
+ buffer: list[str] = []
100
+
101
+ def _flush() -> None:
102
+ if current_path and buffer:
103
+ file_hunks[current_path] = _parse_hunks("\n".join(buffer), include_lines)
104
+
105
+ for line in show_text.splitlines():
106
+ m = _DIFF_FILE_RE.match(line)
107
+ if m:
108
+ _flush()
109
+ current_path = m.group(2)
110
+ buffer = []
111
+ continue
112
+ if current_path is not None:
113
+ buffer.append(line)
114
+ _flush()
115
+ return file_hunks
116
+
117
+
118
+ def _git_diff_hunks(repo: str, path: str, include_lines: bool) -> list[dict[str, object]]:
119
+ """Structured hunks for a pending file_edit. Ranges always; line content when allowed."""
120
+ try:
121
+ result = subprocess.run(
122
+ ["git", "-C", repo, "diff", "--unified=0", "--", path],
123
+ capture_output=True, text=True, timeout=5,
124
+ )
125
+ return _parse_hunks(result.stdout[:_MAX_DIFF_BYTES], include_lines)
126
+ except Exception:
127
+ return []
128
+
129
+
130
+ def _git_show_file_hunks(
131
+ repo: str, sha: str, include_lines: bool,
132
+ ) -> dict[str, list[dict[str, object]]]:
133
+ """Per-file structured hunks for a commit."""
134
+ try:
135
+ result = subprocess.run(
136
+ ["git", "-C", repo, "show", "--format=", "--unified=0", sha],
137
+ capture_output=True, text=True, timeout=10,
138
+ )
139
+ return _parse_show_hunks(result.stdout[:_MAX_DIFF_BYTES * 4], include_lines)
140
+ except Exception:
141
+ return {}
49
142
 
50
143
 
51
144
  def _git_diff_stats(repo: str, path: str) -> tuple[int, int]:
@@ -125,10 +218,14 @@ class _Handler(FileSystemEventHandler):
125
218
 
126
219
  added, removed = _git_diff_stats(self._root, path)
127
220
  lang = Path(event.src_path).suffix.lstrip(".")
221
+ include_lines = base.is_content_captured()
222
+ hunks = _git_diff_hunks(self._root, path, include_lines)
128
223
  meta: dict[str, object] = {
129
224
  "path": path, "language": lang,
130
225
  "lines_added": added, "lines_removed": removed,
131
226
  }
227
+ if hunks:
228
+ meta["hunks"] = hunks
132
229
  diff = _git_diff_content(self._root, path)
133
230
  if diff:
134
231
  meta["diff"] = diff
@@ -192,6 +289,10 @@ def _log_commit(watch_dir: str, sha: str) -> None:
192
289
  meta["parent_hash"] = parent_hash
193
290
  if body and body != subject:
194
291
  meta["body"] = body[:2000]
292
+ include_lines = base.is_content_captured()
293
+ file_hunks = _git_show_file_hunks(watch_dir, sha, include_lines)
294
+ if file_hunks:
295
+ meta["file_hunks"] = file_hunks
195
296
  diff = _git_show_diff(watch_dir, sha)
196
297
  if diff:
197
298
  meta["diff"] = diff
@@ -79,47 +79,37 @@ CAPTURE_DESCRIPTIONS: dict[str, str] = {
79
79
  "code_capture": "Full file diffs and git patches (Pro only, encrypted, private by default)",
80
80
  }
81
81
 
82
- # Content fields that Journal Mode unlocks. When journal_mode is OFF (default),
83
- # these fields are stripped only structural equivalents remain (lengths, counts, types).
84
- # When journal_mode is ON (Pro+), EVERYTHING is persisted and encrypted.
85
- # Journal = the complete, explicit record of the session.
82
+ # Journal content fields no longer gated. All captured metadata is persisted.
83
+ # This list is retained as a reference for the TUI journal enrichment layer,
84
+ # which uses it to identify fields worth displaying as secondary content lines.
86
85
  JOURNAL_CONTENT_FIELDS: list[tuple[str, str]] = [
87
- # AI prompts — full prompt text
88
86
  ("llm_prompt", "prompt_text"),
89
87
  ("agent_prompt", "prompt_preview"),
90
- # AI responses — full completion text
91
88
  ("llm_completion", "response_text"),
92
89
  ("agent_completion", "response_preview"),
93
90
  ("agent_tool_dispatch", "tool_input_preview"),
94
91
  ("agent_tool_result", "result_preview"),
95
92
  ("agent_skill_invoke", "skill_input_preview"),
96
- # Terminal — full command output (command itself is structural, not gated)
97
93
  ("terminal_cmd", "output_snippet"),
98
- # Code — full diffs and commit messages
99
94
  ("file_edit", "diff"),
100
95
  ("git_commit", "diff"),
101
96
  ("git_commit", "message"),
102
- # Web — full search queries, URLs, page titles
97
+ ("git_commit", "body"),
103
98
  ("web_search", "query"),
104
99
  ("web_search", "clicked_results"),
105
100
  ("web_visit", "url"),
106
101
  ("web_visit", "title"),
107
- # Browser — full search queries, URLs, copy content, AI chat input
108
102
  ("browser_search", "query"),
109
103
  ("browser_visit", "url"),
110
104
  ("browser_visit", "title"),
111
105
  ("browser_copy", "text_snippet"),
112
106
  ("browser_ai_chat", "detected_input"),
113
107
  ("browser_ai_chat", "url"),
114
- # Tasks — subject reveals intent
115
108
  ("task_created", "subject"),
116
- # Claude Code hooks — tool input/output and raw user prompt
117
109
  ("user_prompt", "prompt_text"),
118
110
  ("tool_call", "tool_input_preview"),
119
111
  ("tool_result", "result_preview"),
120
- # Agent final message and commit body reveal content
121
112
  ("agent_complete", "last_message_preview"),
122
- ("git_commit", "body"),
123
113
  ]
124
114
 
125
115
 
@@ -99,20 +99,26 @@ _META_EXTRACTORS = {
99
99
  },
100
100
  "PreToolUse": lambda d: {
101
101
  "tool": _TOOL, "tool_name": d.get("tool_name", "unknown"),
102
+ "tool_input": d.get("tool_input") or {},
102
103
  "tool_input_preview": _tool_input_preview(d),
103
104
  },
104
105
  "PostToolUse": lambda d: {
105
106
  "tool": _TOOL, "tool_name": d.get("tool_name", "unknown"), "success": True,
107
+ "tool_input": d.get("tool_input") or {},
108
+ "tool_response": d.get("tool_response") or {},
109
+ "tool_input_preview": _tool_input_preview(d),
106
110
  "result_preview": _extract_result_text(d.get("tool_response")),
107
111
  },
108
112
  "PostToolUseFailure": lambda d: {
109
113
  "tool": _TOOL, "tool_name": d.get("tool_name", "unknown"),
110
114
  "success": False, "is_interrupt": d.get("is_interrupt", False),
115
+ "tool_input": d.get("tool_input") or {},
111
116
  "error": str(d.get("error", ""))[:200],
112
117
  },
113
118
  "SubagentStart": lambda d: {"tool": _TOOL, "agent_type": d.get("agent_type", "unknown"), "agent_id": d.get("agent_id", "")},
114
119
  "SubagentStop": lambda d: {
115
120
  "tool": _TOOL, "agent_type": d.get("agent_type", "unknown"), "agent_id": d.get("agent_id", ""),
121
+ "last_assistant_message": d.get("last_assistant_message", ""),
116
122
  "last_message_preview": str(d.get("last_assistant_message", ""))[:200],
117
123
  },
118
124
  "TaskCreated": lambda d: {"tool": _TOOL, "task_id": d.get("task_id", ""), "subject": d.get("task_subject", "")},
@@ -37,11 +37,62 @@ if command -v jq >/dev/null 2>&1; then
37
37
  ;;
38
38
  PreToolUse)
39
39
  TYPE="tool_call"
40
- META=$(echo "$INPUT" | jq -c '{tool: (.tool_name // "unknown"), tool_use_id: (.tool_use_id // "")}' 2>/dev/null || echo '{}')
40
+ META=$(echo "$INPUT" | jq -c '{
41
+ tool: (.tool_name // "unknown"),
42
+ tool_use_id: (.tool_use_id // ""),
43
+ tool_input: (.tool_input // {}),
44
+ tool_input_preview: (
45
+ (.tool_input // {}) as $ti |
46
+ (.tool_name // "unknown") as $tn |
47
+ (if $tn == "Bash" then ($ti.command // "")
48
+ elif $tn == "Read" then ($ti.file_path // "")
49
+ elif $tn == "Write" then ($ti.file_path // "")
50
+ elif $tn == "Edit" then ($ti.file_path // "")
51
+ elif $tn == "Grep" then (($ti.pattern // "") + " " + ($ti.path // ""))
52
+ elif $tn == "Glob" then ($ti.pattern // "")
53
+ elif $tn == "Agent" then ($ti.description // $ti.prompt // "" | .[0:200])
54
+ else ($ti.command // $ti.file_path // $ti.path // $ti.query // $ti.pattern // $ti.url // $ti.description // $ti.prompt // ($ti | tostring)) end
55
+ ) | tostring | .[0:200]
56
+ )
57
+ }' 2>/dev/null || echo '{}')
41
58
  ;;
42
59
  PostToolUse)
43
60
  TYPE="tool_result"
44
- META=$(echo "$INPUT" | jq -c '{tool: (.tool_name // "unknown"), tool_use_id: (.tool_use_id // "")}' 2>/dev/null || echo '{}')
61
+ META=$(echo "$INPUT" | jq -c '{
62
+ tool: (.tool_name // "unknown"),
63
+ tool_use_id: (.tool_use_id // ""),
64
+ success: true,
65
+ tool_input: (.tool_input // {}),
66
+ tool_response: (.tool_response // {}),
67
+ tool_input_preview: (
68
+ (.tool_input // {}) as $ti |
69
+ (.tool_name // "unknown") as $tn |
70
+ (if $tn == "Bash" then ($ti.command // "")
71
+ elif $tn == "Read" then ($ti.file_path // "")
72
+ elif $tn == "Write" then ($ti.file_path // "")
73
+ elif $tn == "Edit" then ($ti.file_path // "")
74
+ elif $tn == "Grep" then (($ti.pattern // "") + " " + ($ti.path // ""))
75
+ elif $tn == "Glob" then ($ti.pattern // "")
76
+ elif $tn == "Agent" then ($ti.description // $ti.prompt // "" | .[0:200])
77
+ else ($ti.command // $ti.file_path // $ti.path // $ti.query // $ti.pattern // $ti.url // $ti.description // $ti.prompt // ($ti | tostring)) end
78
+ ) | tostring | .[0:200]
79
+ ),
80
+ result_preview: (
81
+ (.tool_response // {}) as $tr |
82
+ (.tool_name // "unknown") as $tn |
83
+ (if $tn == "Bash" then
84
+ (if ($tr.stderr // "") != "" then ("stderr: " + $tr.stderr) else ($tr.stdout // "") end | .[0:200])
85
+ elif $tn == "Read" then
86
+ (($tr.file.numLines // $tr.numLines // 0) | tostring) + " lines"
87
+ elif $tn == "Grep" then
88
+ (($tr.numFiles // 0) | tostring) + " files, " + (($tr.numLines // 0) | tostring) + " lines"
89
+ elif $tn == "Glob" then
90
+ (($tr.numFiles // 0) | tostring) + " files"
91
+ elif ($tr | type) == "string" then ($tr | .[0:200])
92
+ else ($tr | tostring | .[0:200]) end
93
+ )
94
+ )
95
+ }' 2>/dev/null || echo '{}')
45
96
  ;;
46
97
  SubagentStart)
47
98
  TYPE="agent_launch"
@@ -65,7 +116,7 @@ if command -v jq >/dev/null 2>&1; then
65
116
  ;;
66
117
  PostToolUseFailure)
67
118
  TYPE="tool_failure"
68
- META=$(echo "$INPUT" | jq -c '{tool_name: (.tool_name // "unknown"), is_interrupt: (.is_interrupt // false), error: (.error // "" | .[0:200])}' 2>/dev/null || echo '{}')
119
+ META=$(echo "$INPUT" | jq -c '{tool: (.tool_name // "unknown"), is_interrupt: (.is_interrupt // false), success: false, error: (.error // "" | .[0:200])}' 2>/dev/null || echo '{}')
69
120
  ;;
70
121
  SessionEnd)
71
122
  TYPE="claude_session_end"
@@ -0,0 +1,123 @@
1
+ #!/bin/sh
2
+ # MethodProof hook for Gemini CLI — captures prompts, tools, model calls, sessions.
3
+ # Receives JSON on stdin. Posts to local bridge. Fails silently.
4
+ # Must complete in <1s to avoid blocking Gemini.
5
+
6
+ # Skip if no session is running (no pidfile = no daemon = no bridge)
7
+ [ -f "${HOME}/.methodproof/methodproof.pid" ] || exit 0
8
+
9
+ INPUT=$(cat)
10
+
11
+ if command -v jq >/dev/null 2>&1; then
12
+ EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // "unknown"' 2>/dev/null || echo "unknown")
13
+ else
14
+ EVENT=$(echo "$INPUT" | grep -o '"hook_event_name":"[^"]*"' | head -1 | cut -d'"' -f4)
15
+ EVENT=${EVENT:-unknown}
16
+ fi
17
+
18
+ if date +%s.%N >/dev/null 2>&1 && [ "$(date +%N)" != "%N" ]; then
19
+ TS=$(date +%s.%N)
20
+ else
21
+ TS=$(date +%s).000
22
+ fi
23
+
24
+ if command -v jq >/dev/null 2>&1; then
25
+ case "$EVENT" in
26
+ BeforeAgent)
27
+ TYPE="user_prompt"
28
+ META=$(echo "$INPUT" | jq -c '{
29
+ tool: "gemini",
30
+ prompt_preview: (.prompt // "" | .[0:200]),
31
+ prompt_length: (.prompt // "" | length)
32
+ }' 2>/dev/null || echo '{"tool":"gemini"}')
33
+ ;;
34
+ AfterAgent)
35
+ TYPE="agent_completion"
36
+ META=$(echo "$INPUT" | jq -c '{
37
+ tool: "gemini",
38
+ prompt_preview: (.prompt // "" | .[0:200]),
39
+ response_preview: (.prompt_response // "" | .[0:200]),
40
+ response_length: (.prompt_response // "" | length)
41
+ }' 2>/dev/null || echo '{"tool":"gemini"}')
42
+ ;;
43
+ BeforeTool)
44
+ TYPE="tool_call"
45
+ META=$(echo "$INPUT" | jq -c '{
46
+ tool: "gemini",
47
+ tool_name: (.tool_name // "unknown"),
48
+ tool_input_preview: (.tool_input // {} | tostring | .[0:200])
49
+ }' 2>/dev/null || echo '{"tool":"gemini"}')
50
+ ;;
51
+ AfterTool)
52
+ TYPE="tool_result"
53
+ META=$(echo "$INPUT" | jq -c '{
54
+ tool: "gemini",
55
+ tool_name: (.tool_name // "unknown"),
56
+ tool_input_preview: (.tool_input // {} | tostring | .[0:200]),
57
+ result_preview: (.tool_response // {} | tostring | .[0:200])
58
+ }' 2>/dev/null || echo '{"tool":"gemini"}')
59
+ ;;
60
+ BeforeModel)
61
+ TYPE="llm_prompt"
62
+ META=$(echo "$INPUT" | jq -c '{
63
+ tool: "gemini",
64
+ model: "gemini"
65
+ }' 2>/dev/null || echo '{"tool":"gemini","model":"gemini"}')
66
+ ;;
67
+ AfterModel)
68
+ TYPE="llm_completion"
69
+ META=$(echo "$INPUT" | jq -c '{
70
+ tool: "gemini",
71
+ model: "gemini"
72
+ }' 2>/dev/null || echo '{"tool":"gemini","model":"gemini"}')
73
+ ;;
74
+ SessionStart)
75
+ TYPE="gemini_session_start"
76
+ META=$(echo "$INPUT" | jq -c '{
77
+ tool: "gemini",
78
+ gemini_session_id: (.session_id // ""),
79
+ cwd: (.cwd // ""),
80
+ source: (.source // "")
81
+ }' 2>/dev/null || echo '{"tool":"gemini"}')
82
+ ;;
83
+ SessionEnd)
84
+ TYPE="gemini_session_end"
85
+ META=$(echo "$INPUT" | jq -c '{
86
+ tool: "gemini",
87
+ gemini_session_id: (.session_id // ""),
88
+ reason: (.reason // "")
89
+ }' 2>/dev/null || echo '{"tool":"gemini"}')
90
+ ;;
91
+ PreCompress)
92
+ TYPE="context_compact_start"
93
+ META='{"tool":"gemini"}'
94
+ ;;
95
+ Notification)
96
+ TYPE="gemini_event"
97
+ META=$(echo "$INPUT" | jq -c '{
98
+ tool: "gemini",
99
+ event: "notification",
100
+ notification_type: (.notification_type // ""),
101
+ message: (.message // "" | .[0:200])
102
+ }' 2>/dev/null || echo '{"tool":"gemini","event":"notification"}')
103
+ ;;
104
+ *)
105
+ TYPE="gemini_event"
106
+ META="{\"tool\":\"gemini\",\"event\":\"$EVENT\"}"
107
+ ;;
108
+ esac
109
+ else
110
+ TYPE="gemini_event"
111
+ META="{\"tool\":\"gemini\",\"event\":\"$EVENT\"}"
112
+ fi
113
+
114
+ PAYLOAD="{\"events\":[{\"type\":\"$TYPE\",\"timestamp\":$TS,\"metadata\":$META}]}"
115
+ RESPONSE=$(curl -s -w "\n%{http_code}" --max-time 1 --connect-timeout 0.5 \
116
+ -X POST http://localhost:9877/events \
117
+ -H "Content-Type: application/json" \
118
+ -d "$PAYLOAD" 2>/dev/null) || true
119
+ HTTP_CODE=$(echo "$RESPONSE" | tail -1)
120
+ if [ -n "$HTTP_CODE" ] && [ "$HTTP_CODE" != "200" ]; then
121
+ echo "{\"ts\":$TS,\"level\":\"warning\",\"event\":\"hook.post_failed\",\"http_code\":\"$HTTP_CODE\",\"type\":\"$TYPE\",\"payload\":$PAYLOAD}" \
122
+ >> "${HOME}/.methodproof/hook_errors.log" 2>/dev/null || true
123
+ fi
@@ -48,7 +48,11 @@ _CODEX_EVENTS = ["PreToolUse", "PostToolUse", "UserPromptSubmit", "SessionStart"
48
48
  # --- Gemini CLI ---
49
49
  _GEMINI_DIR = Path.home() / ".gemini"
50
50
  _GEMINI_HOOK = _HOOKS_DIR / "gemini_hook.sh"
51
- _GEMINI_EVENTS = ["BeforeTool", "AfterTool", "SessionStart", "SessionEnd"]
51
+ _GEMINI_EVENTS = [
52
+ "BeforeTool", "AfterTool", "BeforeAgent", "AfterAgent",
53
+ "BeforeModel", "AfterModel", "SessionStart", "SessionEnd",
54
+ "PreCompress", "Notification",
55
+ ]
52
56
 
53
57
  # --- Kiro ---
54
58
  _KIRO_DIR = Path.home() / ".kiro"
@@ -266,7 +266,9 @@ def _fmt_meta(ev: dict) -> str:
266
266
  if etype == "tool_result":
267
267
  name = meta.get("tool_name") or meta.get("tool", "")
268
268
  ok = "✓" if meta.get("success", True) else "✗"
269
- return f"{name} {ok}"
269
+ inp = (meta.get("tool_input_preview") or "")[:40]
270
+ result = (meta.get("result_preview") or "")[:40]
271
+ return f"{name} {ok} {inp} {result}".strip()
270
272
  if etype == "tool_failure":
271
273
  name = meta.get("tool_name") or meta.get("tool", "")
272
274
  err = (meta.get("error") or "")[:40]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "methodproof"
3
- version = "0.7.36"
3
+ version = "0.7.38"
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"]
@@ -1,58 +0,0 @@
1
- #!/bin/sh
2
- # MethodProof hook for Gemini CLI — captures tool calls and sessions.
3
- # Receives JSON on stdin. Posts to local bridge. Fails silently.
4
- # Must complete in <1s to avoid blocking Gemini.
5
-
6
- INPUT=$(cat)
7
-
8
- if command -v jq >/dev/null 2>&1; then
9
- EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // "unknown"' 2>/dev/null || echo "unknown")
10
- else
11
- EVENT=$(echo "$INPUT" | grep -o '"hook_event_name":"[^"]*"' | head -1 | cut -d'"' -f4)
12
- EVENT=${EVENT:-unknown}
13
- fi
14
-
15
- if date +%s.%N >/dev/null 2>&1 && [ "$(date +%N)" != "%N" ]; then
16
- TS=$(date +%s.%N)
17
- else
18
- TS=$(date +%s).000
19
- fi
20
-
21
- if command -v jq >/dev/null 2>&1; then
22
- case "$EVENT" in
23
- BeforeTool)
24
- TYPE="tool_call"
25
- META=$(echo "$INPUT" | jq -c '{tool: "gemini", tool_name: (.tool_name // "unknown")}' 2>/dev/null || echo '{"tool":"gemini"}')
26
- ;;
27
- AfterTool)
28
- TYPE="tool_result"
29
- META=$(echo "$INPUT" | jq -c '{tool: "gemini", tool_name: (.tool_name // "unknown")}' 2>/dev/null || echo '{"tool":"gemini"}')
30
- ;;
31
- SessionStart)
32
- TYPE="gemini_session_start"
33
- META=$(echo "$INPUT" | jq -c '{tool: "gemini", cwd: (.cwd // "")}' 2>/dev/null || echo '{"tool":"gemini"}')
34
- ;;
35
- SessionEnd)
36
- TYPE="gemini_session_end"
37
- META=$(echo "$INPUT" | jq -c '{tool: "gemini", reason: (.reason // "")}' 2>/dev/null || echo '{"tool":"gemini"}')
38
- ;;
39
- *)
40
- TYPE="gemini_event"
41
- META="{\"tool\":\"gemini\",\"event\":\"$EVENT\"}"
42
- ;;
43
- esac
44
- else
45
- TYPE="gemini_event"
46
- META="{\"tool\":\"gemini\",\"event\":\"$EVENT\"}"
47
- fi
48
-
49
- PAYLOAD="{\"events\":[{\"type\":\"$TYPE\",\"timestamp\":$TS,\"metadata\":$META}]}"
50
- RESPONSE=$(curl -s -w "\n%{http_code}" --max-time 1 --connect-timeout 0.5 \
51
- -X POST http://localhost:9877/events \
52
- -H "Content-Type: application/json" \
53
- -d "$PAYLOAD" 2>/dev/null) || true
54
- HTTP_CODE=$(echo "$RESPONSE" | tail -1)
55
- if [ -n "$HTTP_CODE" ] && [ "$HTTP_CODE" != "200" ]; then
56
- echo "{\"ts\":$TS,\"level\":\"warning\",\"event\":\"hook.post_failed\",\"http_code\":\"$HTTP_CODE\",\"type\":\"$TYPE\",\"payload\":$PAYLOAD}" \
57
- >> "${HOME}/.methodproof/hook_errors.log" 2>/dev/null || true
58
- fi
File without changes
File without changes
File without changes