methodproof 0.7.37__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.37 → methodproof-0.7.38}/CHANGELOG.md +10 -0
  2. {methodproof-0.7.37 → methodproof-0.7.38}/PKG-INFO +1 -1
  3. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/agents/base.py +7 -0
  4. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/agents/watcher.py +101 -0
  5. methodproof-0.7.38/methodproof/hooks/gemini_hook.sh +123 -0
  6. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/hooks/install.py +5 -1
  7. {methodproof-0.7.37 → methodproof-0.7.38}/pyproject.toml +1 -1
  8. methodproof-0.7.37/methodproof/hooks/gemini_hook.sh +0 -58
  9. {methodproof-0.7.37 → methodproof-0.7.38}/.github/workflows/ci.yml +0 -0
  10. {methodproof-0.7.37 → methodproof-0.7.38}/.gitignore +0 -0
  11. {methodproof-0.7.37 → methodproof-0.7.38}/LICENSE +0 -0
  12. {methodproof-0.7.37 → methodproof-0.7.38}/README.md +0 -0
  13. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/__init__.py +0 -0
  14. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/__main__.py +0 -0
  15. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/_daemon.py +0 -0
  16. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/agents/__init__.py +0 -0
  17. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/agents/music.py +0 -0
  18. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/agents/terminal.py +0 -0
  19. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/analysis.py +0 -0
  20. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/binding.py +0 -0
  21. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/bip39.py +0 -0
  22. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/bridge.py +0 -0
  23. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/cli.py +0 -0
  24. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/config.py +0 -0
  25. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/crypto.py +0 -0
  26. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/e2e.py +0 -0
  27. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/graph.py +0 -0
  28. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/hook.py +0 -0
  29. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/hooks/__init__.py +0 -0
  30. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/hooks/claude_code.py +0 -0
  31. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/hooks/claude_code.sh +0 -0
  32. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/hooks/cline_hook.sh +0 -0
  33. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/hooks/codex_hook.sh +0 -0
  34. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/hooks/kiro_hook.sh +0 -0
  35. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/hooks/mcp_register.py +0 -0
  36. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/hooks/openclaw/HOOK.md +0 -0
  37. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/hooks/openclaw/handler.ts +0 -0
  38. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/hooks/openclaw_install.py +0 -0
  39. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/hooks/opencode_plugin.js +0 -0
  40. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/hooks/wrappers.py +0 -0
  41. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/integrity.py +0 -0
  42. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/kdf.py +0 -0
  43. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/keychain.py +0 -0
  44. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/live.py +0 -0
  45. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/lock.py +0 -0
  46. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/mcp.py +0 -0
  47. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/migrate_db.py +0 -0
  48. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/proxy.py +0 -0
  49. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/proxy_daemon.py +0 -0
  50. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/repos.py +0 -0
  51. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/skills/methodproof/SKILL.md +0 -0
  52. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/store.py +0 -0
  53. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/sync.py +0 -0
  54. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/tui/__init__.py +0 -0
  55. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/tui/consent.py +0 -0
  56. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/tui/init.py +0 -0
  57. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/tui/log.py +0 -0
  58. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/tui/login_success.py +0 -0
  59. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/tui/review.py +0 -0
  60. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/tui/start.py +0 -0
  61. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/tui/status.py +0 -0
  62. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/tui/theme.py +0 -0
  63. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/viewer.py +0 -0
  64. {methodproof-0.7.37 → methodproof-0.7.38}/methodproof/wordlist.py +0 -0
  65. {methodproof-0.7.37 → methodproof-0.7.38}/test_windows_compat.py +0 -0
  66. {methodproof-0.7.37 → methodproof-0.7.38}/tests/__init__.py +0 -0
  67. {methodproof-0.7.37 → methodproof-0.7.38}/tests/conftest.py +0 -0
  68. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_analysis.py +0 -0
  69. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_cli_auth.py +0 -0
  70. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_cli_config.py +0 -0
  71. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_cli_helpers.py +0 -0
  72. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_cli_session.py +0 -0
  73. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_cli_share.py +0 -0
  74. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_cli_start.py +0 -0
  75. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_cli_update.py +0 -0
  76. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_e2e_integration.py +0 -0
  77. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_graph.py +0 -0
  78. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_hooks.py +0 -0
  79. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_live.py +0 -0
  80. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_openclaw_hooks.py +0 -0
  81. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_profiles.py +0 -0
  82. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_security.py +0 -0
  83. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_store.py +0 -0
  84. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_sync.py +0 -0
  85. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_viewer.py +0 -0
  86. {methodproof-0.7.37 → methodproof-0.7.38}/tests/test_wrappers.py +0 -0
  87. {methodproof-0.7.37 → methodproof-0.7.38}/uv.lock +0 -0
@@ -1,5 +1,15 @@
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
+
3
13
  ## [0.7.37] — 2026-04-12
4
14
 
5
15
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: methodproof
3
- Version: 0.7.37
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
@@ -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)
@@ -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
@@ -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"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "methodproof"
3
- version = "0.7.37"
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
File without changes