methodproof 0.3.0__tar.gz → 0.3.3__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 (50) hide show
  1. methodproof-0.3.3/CHANGELOG.md +89 -0
  2. methodproof-0.3.0/README.md → methodproof-0.3.3/PKG-INFO +33 -0
  3. methodproof-0.3.0/PKG-INFO → methodproof-0.3.3/README.md +18 -16
  4. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/agents/base.py +1 -0
  5. methodproof-0.3.3/methodproof/bridge.py +200 -0
  6. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/cli.py +250 -37
  7. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/hooks/claude_code.sh +10 -5
  8. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/live.py +4 -4
  9. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/store.py +11 -0
  10. methodproof-0.3.3/methodproof/viewer.py +141 -0
  11. {methodproof-0.3.0 → methodproof-0.3.3}/pyproject.toml +2 -3
  12. methodproof-0.3.3/tests/test_live.py +148 -0
  13. {methodproof-0.3.0 → methodproof-0.3.3}/uv.lock +4 -6
  14. methodproof-0.3.0/CHANGELOG.md +0 -20
  15. methodproof-0.3.0/methodproof/bridge.py +0 -86
  16. methodproof-0.3.0/methodproof/viewer.py +0 -239
  17. {methodproof-0.3.0 → methodproof-0.3.3}/.github/workflows/ci.yml +0 -0
  18. {methodproof-0.3.0 → methodproof-0.3.3}/.gitignore +0 -0
  19. {methodproof-0.3.0 → methodproof-0.3.3}/LICENSE +0 -0
  20. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/__init__.py +0 -0
  21. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/__main__.py +0 -0
  22. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/agents/__init__.py +0 -0
  23. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/agents/music.py +0 -0
  24. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/agents/terminal.py +0 -0
  25. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/agents/watcher.py +0 -0
  26. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/analysis.py +0 -0
  27. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/config.py +0 -0
  28. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/crypto.py +0 -0
  29. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/graph.py +0 -0
  30. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/hook.py +0 -0
  31. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/hooks/__init__.py +0 -0
  32. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/hooks/claude_code.py +0 -0
  33. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/hooks/install.py +0 -0
  34. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/hooks/openclaw/HOOK.md +0 -0
  35. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/hooks/openclaw/handler.ts +0 -0
  36. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/hooks/openclaw_install.py +0 -0
  37. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/hooks/wrappers.py +0 -0
  38. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/integrity.py +0 -0
  39. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/mcp.py +0 -0
  40. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/repos.py +0 -0
  41. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/skills/methodproof/SKILL.md +0 -0
  42. {methodproof-0.3.0 → methodproof-0.3.3}/methodproof/sync.py +0 -0
  43. {methodproof-0.3.0 → methodproof-0.3.3}/test_windows_compat.py +0 -0
  44. {methodproof-0.3.0 → methodproof-0.3.3}/tests/__init__.py +0 -0
  45. {methodproof-0.3.0 → methodproof-0.3.3}/tests/test_analysis.py +0 -0
  46. {methodproof-0.3.0 → methodproof-0.3.3}/tests/test_graph.py +0 -0
  47. {methodproof-0.3.0 → methodproof-0.3.3}/tests/test_hooks.py +0 -0
  48. {methodproof-0.3.0 → methodproof-0.3.3}/tests/test_openclaw_hooks.py +0 -0
  49. {methodproof-0.3.0 → methodproof-0.3.3}/tests/test_store.py +0 -0
  50. {methodproof-0.3.0 → methodproof-0.3.3}/tests/test_wrappers.py +0 -0
@@ -0,0 +1,89 @@
1
+ # Changelog
2
+
3
+ ## [0.3.3] — 2026-04-05
4
+
5
+ ### Fixed
6
+ - macOS hook timestamp: `date +%s.%3N` produced invalid JSON (literal `.3N`), silently dropping all tool_call, tool_result, agent_launch, and agent_complete events
7
+ - Stale session recovery: `mp start` now detects dead daemons and cleans up instead of blocking
8
+ - Bridge events now route through `base.emit()` for consent gating, hash chain integrity, and live streaming
9
+
10
+ ### Changed
11
+ - `mp view` replaced with terminal-based session audit (no HTTP server)
12
+ - Recording threads start after fork (fixes silent 0-event sessions on macOS)
13
+ - Hook errors logged to `~/.methodproof/hook_errors.log` for inspection
14
+ - Consent-blocked events logged at debug level
15
+
16
+ ### Added
17
+ - Watch Scope section in README documenting directory scope and exclusion patterns
18
+ - `store.reset_connection()` for fork-safe SQLite WAL handling
19
+
20
+ ## [0.3.2] — 2026-04-04
21
+
22
+ ### Changed
23
+ - `websocket-client` is now a default dependency (no longer requires `pip install methodproof[live]`)
24
+ - `mp start --live` prints a clickable dashboard URL instead of the API host
25
+ - `live.start()` returns the dashboard URL from the platform handshake
26
+
27
+ ## [0.3.1] — 2026-04-04
28
+
29
+ ### Added
30
+ - `mp extension pair` — pair browser extension to active session
31
+ - `mp extension status` — check extension connection
32
+ - `mp extension install` — open Chrome Web Store listing
33
+ - Bridge auto-discovery: extension auto-connects when CLI session starts
34
+ - Bridge `/pair/auto` endpoint for extension auto-pairing
35
+ - Bridge `/pair/register` + `/pair/ack` for manual pairing flow
36
+ - `mp start` now runs in the background (daemon mode on Unix)
37
+ - Extension status check on session start (connected/not detected)
38
+ - `SO_REUSEADDR` on bridge socket for clean restarts
39
+
40
+ ### Changed
41
+ - Bridge accepts API credentials for auto-discovery passthrough
42
+ - `_shutdown` handler wrapped in try/except for robust daemon cleanup
43
+
44
+ ## [0.3.0] — 2026-04-03
45
+
46
+ ### Added
47
+ - Prompt structural analysis: 35 metadata dimensions extracted from AI prompts
48
+ - Environment profiling: structural scan of AI dev environment (instruction files, tool configs)
49
+ - `environment_analysis` consent category (default on, structural metadata only)
50
+ - Outcome metrics: correlation between prompt patterns and session results
51
+ - Consent review prompt when new capture categories exist after update
52
+
53
+ ### Changed
54
+ - README rebrand: OG dark banner, hero process graph, brand-colored badges
55
+
56
+ ### Fixed
57
+ - Unused import, silent failures, operator precedence, magic numbers (code review)
58
+
59
+ ## [0.2.0] — 2026-03-30
60
+
61
+ ### Added
62
+ - Browser login via device auth flow (`mp login` opens browser)
63
+ - Three-section interactive consent (capture, research, redaction)
64
+ - `mp uninstall` — remove all hooks, data, and config
65
+ - Color-coded command reference (`mp help`)
66
+ - Auto-refresh expired API tokens
67
+ - `mp update` — self-update from PyPI
68
+ - AI Agent Graph branding for prompt/response events
69
+ - Code capture consent category (Pro only, encrypted, default off)
70
+ - Windows compatibility (stop sentinel, icacls permissions)
71
+ - `--live` flag streams events to platform in real-time over WebSocket
72
+ - `live` optional dependency (`pip install methodproof[live]`)
73
+ - Free-tier full-spectrum consent unlocks 30-day rolling live stream
74
+
75
+ ## [0.1.1] — 2026-03-28
76
+
77
+ ### Fixed
78
+ - Added pytest dev dependency
79
+
80
+ ## [0.1.0] — 2026-03-27
81
+
82
+ ### Added
83
+ - Initial release
84
+ - `mp init/start/stop/view/log/push/publish/review/consent/tag/delete`
85
+ - File watcher, terminal monitor, music agent
86
+ - Local bridge for browser extension events
87
+ - Hash-chained events + Ed25519 attestation
88
+ - Full Spectrum messaging (all 10 categories)
89
+ - `mp mcp-serve` — MCP server for Claude Code
@@ -1,3 +1,18 @@
1
+ Metadata-Version: 2.4
2
+ Name: methodproof
3
+ Version: 0.3.3
4
+ Summary: See how you code. Capture and visualize your engineering process.
5
+ License-Expression: Apache-2.0
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: watchdog>=4.0
9
+ Requires-Dist: websocket-client>=1.7
10
+ Provides-Extra: e2e
11
+ Requires-Dist: cryptography>=43.0; extra == 'e2e'
12
+ Provides-Extra: signing
13
+ Requires-Dist: cryptography>=43.0; extra == 'signing'
14
+ Description-Content-Type: text/markdown
15
+
1
16
  <p align="center">
2
17
  <img src="https://cdn.methodproof.com/og/og-primary-dark.png" alt="MethodProof — Engineering Process Intelligence" width="720" />
3
18
  </p>
@@ -191,6 +206,24 @@ All sensitive metadata (prompts, completions, commands, output, diffs) is encryp
191
206
  - **AI CLIs** — codex, gemini, aider command wrappers
192
207
  - **MCP server** — registered with Claude Code for session/graph queries
193
208
 
209
+ ## Watch Scope
210
+
211
+ `methodproof start` watches the **current directory recursively** (or the directory passed via `--dir`). Every file create, edit, and delete under that tree generates an event.
212
+
213
+ **Start in the right directory.** If you start in a monorepo root, you'll capture events from every subdirectory. If you start in a subdirectory, parent-level changes won't be recorded.
214
+
215
+ ```bash
216
+ cd my-project # scope to this project
217
+ methodproof start
218
+
219
+ cd ~/code # ⚠️ captures ALL projects under ~/code
220
+ methodproof start
221
+ ```
222
+
223
+ **Excluded patterns:** `__pycache__`, `.pyc`, `.git/`, `node_modules`, `.DS_Store`, `.swp`, temp files ending in `~`
224
+
225
+ **Git commits** are detected by polling `.git/refs/heads/` every 2 seconds — only commits in a git repo rooted at (or above) the watch directory are captured.
226
+
194
227
  ## Data Directory
195
228
 
196
229
  `~/.methodproof/`
@@ -1,19 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: methodproof
3
- Version: 0.3.0
4
- Summary: See how you code. Capture and visualize your engineering process.
5
- License-Expression: Apache-2.0
6
- License-File: LICENSE
7
- Requires-Python: >=3.11
8
- Requires-Dist: watchdog>=4.0
9
- Provides-Extra: e2e
10
- Requires-Dist: cryptography>=43.0; extra == 'e2e'
11
- Provides-Extra: live
12
- Requires-Dist: websocket-client>=1.7; extra == 'live'
13
- Provides-Extra: signing
14
- Requires-Dist: cryptography>=43.0; extra == 'signing'
15
- Description-Content-Type: text/markdown
16
-
17
1
  <p align="center">
18
2
  <img src="https://cdn.methodproof.com/og/og-primary-dark.png" alt="MethodProof — Engineering Process Intelligence" width="720" />
19
3
  </p>
@@ -207,6 +191,24 @@ All sensitive metadata (prompts, completions, commands, output, diffs) is encryp
207
191
  - **AI CLIs** — codex, gemini, aider command wrappers
208
192
  - **MCP server** — registered with Claude Code for session/graph queries
209
193
 
194
+ ## Watch Scope
195
+
196
+ `methodproof start` watches the **current directory recursively** (or the directory passed via `--dir`). Every file create, edit, and delete under that tree generates an event.
197
+
198
+ **Start in the right directory.** If you start in a monorepo root, you'll capture events from every subdirectory. If you start in a subdirectory, parent-level changes won't be recorded.
199
+
200
+ ```bash
201
+ cd my-project # scope to this project
202
+ methodproof start
203
+
204
+ cd ~/code # ⚠️ captures ALL projects under ~/code
205
+ methodproof start
206
+ ```
207
+
208
+ **Excluded patterns:** `__pycache__`, `.pyc`, `.git/`, `node_modules`, `.DS_Store`, `.swp`, temp files ending in `~`
209
+
210
+ **Git commits** are detected by polling `.git/refs/heads/` every 2 seconds — only commits in a git repo rooted at (or above) the watch directory are captured.
211
+
210
212
  ## Data Directory
211
213
 
212
214
  `~/.methodproof/`
@@ -83,6 +83,7 @@ def emit(event_type: str, metadata: dict[str, Any]) -> None:
83
83
  # Event-level consent gate
84
84
  gate = _EVENT_GATES.get(event_type)
85
85
  if gate and not _capture.get(gate, True):
86
+ log("debug", "emit.consent_blocked", type=event_type, gate=gate)
86
87
  return
87
88
  # Field-level consent gate — strip opted-out fields
88
89
  for category, pairs in _FIELD_GATES.items():
@@ -0,0 +1,200 @@
1
+ """Local HTTP bridge — accepts browser extension events into SQLite and handles pairing."""
2
+
3
+ import json
4
+ import secrets
5
+ import threading
6
+ from http.server import BaseHTTPRequestHandler, HTTPServer
7
+ from typing import Any
8
+
9
+ from methodproof.agents import base
10
+
11
+ _session_id = ""
12
+ _api_token = ""
13
+ _api_base = ""
14
+ _e2e_key = ""
15
+ _pairing: dict[str, Any] = {} # {token: {session_id, api_token, api_base, e2e_key, paired}}
16
+ _extension_paired = threading.Event()
17
+ MAX_BODY = 10 * 1024 * 1024 # 10 MB
18
+
19
+ PAIR_PAGE = """<!DOCTYPE html>
20
+ <html>
21
+ <head>
22
+ <meta charset="utf-8">
23
+ <title>MethodProof — Pair Extension</title>
24
+ <style>
25
+ body {{ font-family: Inter, system-ui, sans-serif; background: #faf9f7; color: #0a0a0a;
26
+ display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; }}
27
+ .card {{ text-align: center; padding: 48px; max-width: 400px; }}
28
+ h1 {{ font-size: 18px; margin: 0 0 8px; }}
29
+ h1 span {{ font-family: Laila, serif; font-weight: 400; }}
30
+ .status {{ font-size: 14px; color: #666; margin: 24px 0; }}
31
+ .paired {{ color: #2d7a42; font-weight: 600; }}
32
+ .session {{ font-family: 'IBM Plex Mono', monospace; font-size: 12px; color: #888;
33
+ background: #f0eeea; padding: 8px 12px; display: inline-block; }}
34
+ .spinner {{ display: inline-block; width: 16px; height: 16px; border: 2px solid #ddd;
35
+ border-top-color: #d93326; border-radius: 50%; animation: spin 0.8s linear infinite;
36
+ vertical-align: middle; margin-right: 8px; }}
37
+ @keyframes spin {{ to {{ transform: rotate(360deg); }} }}
38
+ </style>
39
+ </head>
40
+ <body>
41
+ <div class="card">
42
+ <h1><b>Method</b><span>Proof</span></h1>
43
+ <div class="session">{session_short}</div>
44
+ <div id="status" class="status"><span class="spinner"></span>Pairing extension...</div>
45
+ <div id="methodproof-pair-data"
46
+ data-session-id="{session_id}"
47
+ data-token="{api_token}"
48
+ data-api-base="{api_base}"
49
+ data-e2e-key="{e2e_key}"
50
+ style="display:none"></div>
51
+ </div>
52
+ <script>
53
+ window.addEventListener('methodproof-paired', function() {{
54
+ document.getElementById('status').className = 'status paired';
55
+ document.getElementById('status').innerHTML = '&#10003; Extension paired';
56
+ fetch('/pair/ack', {{method: 'POST', headers: {{'Content-Type': 'application/json'}},
57
+ body: JSON.stringify({{token: '{pair_token}'}}) }});
58
+ setTimeout(function() {{ window.close(); }}, 2000);
59
+ }});
60
+ </script>
61
+ </body>
62
+ </html>"""
63
+
64
+
65
+ def generate_pair_token(session_id: str, api_token: str, api_base: str, e2e_key: str = "") -> str:
66
+ token = secrets.token_urlsafe(16)
67
+ _pairing[token] = {
68
+ "session_id": session_id, "api_token": api_token,
69
+ "api_base": api_base, "e2e_key": e2e_key, "paired": False,
70
+ }
71
+ return token
72
+
73
+
74
+ class _Handler(BaseHTTPRequestHandler):
75
+ def log_message(self, fmt: str, *args: Any) -> None:
76
+ pass
77
+
78
+ def do_GET(self) -> None:
79
+ if self.path == "/session":
80
+ self._json({"session_id": _session_id, "active": bool(_session_id)})
81
+ elif self.path.startswith("/pair?token="):
82
+ token = self.path.split("token=", 1)[1].split("&")[0]
83
+ data = _pairing.get(token)
84
+ if not data:
85
+ self.send_error(403, "Invalid or expired pairing token")
86
+ return
87
+ html = PAIR_PAGE.format(
88
+ session_id=data["session_id"],
89
+ session_short=data["session_id"][:8],
90
+ api_token=data["api_token"],
91
+ api_base=data["api_base"],
92
+ e2e_key=data["e2e_key"],
93
+ pair_token=token,
94
+ ).encode()
95
+ self.send_response(200)
96
+ self.send_header("Content-Type", "text/html")
97
+ self._cors()
98
+ self.end_headers()
99
+ self.wfile.write(html)
100
+ elif self.path == "/pair/auto":
101
+ if not _session_id:
102
+ self._json({"active": False})
103
+ else:
104
+ self._json({
105
+ "active": True,
106
+ "session_id": _session_id,
107
+ "token": _api_token,
108
+ "api_base": _api_base,
109
+ "e2e_key": _e2e_key,
110
+ })
111
+ _extension_paired.set()
112
+ base.log("info", "extension.auto_paired", session_id=_session_id)
113
+ elif self.path == "/extension-status":
114
+ self._json({"paired": _extension_paired.is_set()})
115
+ else:
116
+ self.send_error(404)
117
+
118
+ def do_POST(self) -> None:
119
+ length = int(self.headers.get("Content-Length", 0))
120
+ if length > MAX_BODY:
121
+ self.send_error(413, "Request too large")
122
+ return
123
+ try:
124
+ body = json.loads(self.rfile.read(length)) if length else {}
125
+ except (json.JSONDecodeError, ValueError):
126
+ self.send_error(400, "Invalid JSON")
127
+ return
128
+
129
+ if self.path == "/events":
130
+ events = body.get("events", [])
131
+ accepted = 0
132
+ for e in events:
133
+ etype = e.get("type", "unknown")
134
+ meta = e.get("metadata", {})
135
+ if "duration_ms" in e:
136
+ meta["duration_ms"] = e["duration_ms"]
137
+ base.emit(etype, meta)
138
+ accepted += 1
139
+ self._json({"accepted": accepted})
140
+ elif self.path == "/pair/register":
141
+ token = body.get("token", "")
142
+ if not token or not body.get("session_id"):
143
+ self.send_error(400, "Missing token or session_id")
144
+ return
145
+ _pairing[token] = {
146
+ "session_id": body["session_id"],
147
+ "api_token": body.get("api_token", ""),
148
+ "api_base": body.get("api_base", ""),
149
+ "e2e_key": body.get("e2e_key", ""),
150
+ "paired": False,
151
+ }
152
+ self._json({"ok": True, "token": token})
153
+ elif self.path == "/pair/ack":
154
+ token = body.get("token", "")
155
+ data = _pairing.get(token)
156
+ if data:
157
+ data["paired"] = True
158
+ _extension_paired.set()
159
+ base.log("info", "extension.paired", session_id=data["session_id"])
160
+ self._json({"ok": bool(data)})
161
+ else:
162
+ self.send_error(404)
163
+
164
+ def do_OPTIONS(self) -> None:
165
+ self.send_response(204)
166
+ self._cors()
167
+ self.end_headers()
168
+
169
+ def _json(self, data: Any) -> None:
170
+ body = json.dumps(data).encode()
171
+ self.send_response(200)
172
+ self.send_header("Content-Type", "application/json")
173
+ self._cors()
174
+ self.end_headers()
175
+ self.wfile.write(body)
176
+
177
+ def _cors(self) -> None:
178
+ origin = self.headers.get("Origin", "")
179
+ allowed = origin.startswith("chrome-extension://") or origin.startswith("http://localhost")
180
+ self.send_header("Access-Control-Allow-Origin", origin if allowed else "http://localhost:9877")
181
+ self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
182
+ self.send_header("Access-Control-Allow-Headers", "Content-Type")
183
+
184
+
185
+ def start(session_id: str, stop: threading.Event, port: int = 9877,
186
+ api_token: str = "", api_base: str = "", e2e_key: str = "") -> None:
187
+ global _session_id, _api_token, _api_base, _e2e_key
188
+ _session_id = session_id
189
+ _api_token = api_token
190
+ _api_base = api_base
191
+ _e2e_key = e2e_key
192
+ _extension_paired.clear()
193
+ HTTPServer.allow_reuse_address = True
194
+ server = HTTPServer(("127.0.0.1", port), _Handler)
195
+ server.timeout = 1
196
+ base.log("info", "bridge.started", port=port)
197
+ while not stop.is_set():
198
+ server.handle_request()
199
+ server.server_close()
200
+ base.log("info", "bridge.stopped")