methodproof 0.8.5__tar.gz → 0.8.7__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 (90) hide show
  1. {methodproof-0.8.5 → methodproof-0.8.7}/CHANGELOG.md +10 -0
  2. {methodproof-0.8.5 → methodproof-0.8.7}/PKG-INFO +1 -1
  3. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/cli.py +53 -1
  4. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/hooks/claude_code.py +1 -0
  5. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/sync.py +66 -2
  6. {methodproof-0.8.5 → methodproof-0.8.7}/pyproject.toml +1 -1
  7. {methodproof-0.8.5 → methodproof-0.8.7}/uv.lock +1 -1
  8. {methodproof-0.8.5 → methodproof-0.8.7}/.github/workflows/ci.yml +0 -0
  9. {methodproof-0.8.5 → methodproof-0.8.7}/.gitignore +0 -0
  10. {methodproof-0.8.5 → methodproof-0.8.7}/LICENSE +0 -0
  11. {methodproof-0.8.5 → methodproof-0.8.7}/README.md +0 -0
  12. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/__init__.py +0 -0
  13. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/__main__.py +0 -0
  14. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/_daemon.py +0 -0
  15. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/agents/__init__.py +0 -0
  16. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/agents/base.py +0 -0
  17. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/agents/music.py +0 -0
  18. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/agents/terminal.py +0 -0
  19. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/agents/watcher.py +0 -0
  20. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/analysis.py +0 -0
  21. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/binding.py +0 -0
  22. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/bip39.py +0 -0
  23. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/bridge.py +0 -0
  24. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/config.py +0 -0
  25. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/crypto.py +0 -0
  26. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/e2e.py +0 -0
  27. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/graph.py +0 -0
  28. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/hook.py +0 -0
  29. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/hooks/__init__.py +0 -0
  30. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/hooks/claude_code.sh +0 -0
  31. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/hooks/cline_hook.sh +0 -0
  32. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/hooks/codex_hook.sh +0 -0
  33. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/hooks/gemini_hook.sh +0 -0
  34. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/hooks/install.py +0 -0
  35. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/hooks/kiro_hook.sh +0 -0
  36. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/hooks/mcp_register.py +0 -0
  37. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/hooks/model_cache.py +0 -0
  38. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/hooks/openclaw/HOOK.md +0 -0
  39. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/hooks/openclaw/handler.ts +0 -0
  40. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/hooks/openclaw_install.py +0 -0
  41. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/hooks/opencode_plugin.js +0 -0
  42. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/hooks/wrappers.py +0 -0
  43. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/integrity.py +0 -0
  44. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/kdf.py +0 -0
  45. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/keychain.py +0 -0
  46. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/live.py +0 -0
  47. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/lock.py +0 -0
  48. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/mcp.py +0 -0
  49. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/migrate_db.py +0 -0
  50. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/proxy.py +0 -0
  51. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/proxy_daemon.py +0 -0
  52. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/repos.py +0 -0
  53. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/skills/methodproof/SKILL.md +0 -0
  54. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/store.py +0 -0
  55. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/tui/__init__.py +0 -0
  56. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/tui/consent.py +0 -0
  57. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/tui/init.py +0 -0
  58. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/tui/log.py +0 -0
  59. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/tui/login_success.py +0 -0
  60. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/tui/review.py +0 -0
  61. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/tui/start.py +0 -0
  62. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/tui/status.py +0 -0
  63. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/tui/theme.py +0 -0
  64. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/viewer.py +0 -0
  65. {methodproof-0.8.5 → methodproof-0.8.7}/methodproof/wordlist.py +0 -0
  66. {methodproof-0.8.5 → methodproof-0.8.7}/test_windows_compat.py +0 -0
  67. {methodproof-0.8.5 → methodproof-0.8.7}/tests/__init__.py +0 -0
  68. {methodproof-0.8.5 → methodproof-0.8.7}/tests/conftest.py +0 -0
  69. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_analysis.py +0 -0
  70. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_cli_auth.py +0 -0
  71. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_cli_config.py +0 -0
  72. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_cli_helpers.py +0 -0
  73. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_cli_session.py +0 -0
  74. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_cli_share.py +0 -0
  75. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_cli_start.py +0 -0
  76. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_cli_update.py +0 -0
  77. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_e2e_integration.py +0 -0
  78. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_graph.py +0 -0
  79. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_hooks.py +0 -0
  80. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_live.py +0 -0
  81. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_model_cache.py +0 -0
  82. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_openclaw_hooks.py +0 -0
  83. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_profiles.py +0 -0
  84. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_repos.py +0 -0
  85. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_security.py +0 -0
  86. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_store.py +0 -0
  87. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_sync.py +0 -0
  88. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_viewer.py +0 -0
  89. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_watcher_ignore.py +0 -0
  90. {methodproof-0.8.5 → methodproof-0.8.7}/tests/test_wrappers.py +0 -0
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.7] — 2026-05-15
4
+
5
+ ### Fixed
6
+ - **`SubagentStop` events now emit `last_message_preview`** (`methodproof/hooks/claude_code.py`) — the Python hook only emitted full `last_assistant_message`, so the dashboard's structural-only view of `agent_complete` events showed nothing for accounts without journal mode. Hook now emits both: full content (server gates by tier) plus a 200-char preview (always survives).
7
+
8
+ ## [0.8.6] — 2026-04-24
9
+
10
+ ### Fixed
11
+ - **`mp push` sends real capture-time boundaries** — `POST /personal/sessions` now includes `started_at` from the local `sessions.created_at`, and `PUT /complete` now includes `ended_at` from `sessions.completed_at` (when available). Previously the server stamped both boundaries at push time, so the dashboard's `/personal/sessions` showed a duration equal to how long the push transaction took instead of the real session window. Requires platform ≥ 2026-04-24; older platforms silently ignore the new fields.
12
+
3
13
  ## [0.8.1] — 2026-04-17
4
14
 
5
15
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: methodproof
3
- Version: 0.8.5
3
+ Version: 0.8.7
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
@@ -1929,6 +1929,54 @@ def _push_one(sid: str, cfg: dict, force: bool = False) -> None:
1929
1929
  print(f" Publish: mp publish {sid[:8]}")
1930
1930
 
1931
1931
 
1932
+ def cmd_resync(args: argparse.Namespace) -> None:
1933
+ """Re-push already-synced sessions to overwrite server ciphertext with
1934
+ plaintext. Use when a past migration encrypted sensitive fields with a
1935
+ key the server can't read."""
1936
+ local = getattr(args, "local", False)
1937
+ cfg = config.load(local=local)
1938
+ if not cfg.get("token"):
1939
+ print("Run `methodproof login` first.")
1940
+ sys.exit(1)
1941
+ if cfg.get("e2e_mode"):
1942
+ print("E2E mode is ON — resync would preserve ciphertext. Turn off with `mp e2e off` first.")
1943
+ sys.exit(1)
1944
+
1945
+ from methodproof.sync import resync_events
1946
+
1947
+ sessions = store.list_sessions()
1948
+ candidates = [s for s in sessions if s.get("synced") and s.get("remote_id")]
1949
+
1950
+ if args.session_id:
1951
+ pref = args.session_id
1952
+ candidates = [s for s in candidates
1953
+ if s["id"].startswith(pref) or (s.get("remote_id") or "").startswith(pref)]
1954
+ if not candidates:
1955
+ print(f"No synced session matches {args.session_id}")
1956
+ sys.exit(1)
1957
+ elif args.since:
1958
+ from datetime import datetime
1959
+ cutoff = datetime.fromisoformat(args.since).timestamp()
1960
+ candidates = [s for s in candidates
1961
+ if (s.get("completed_at") or s.get("created_at") or 0) >= cutoff]
1962
+
1963
+ if not candidates:
1964
+ print("No synced sessions to resync.")
1965
+ return
1966
+
1967
+ total_events = 0
1968
+ print(f"Resyncing {len(candidates)} sessions (decrypting sensitive fields on push)...")
1969
+ for s in candidates:
1970
+ try:
1971
+ n = resync_events(s, cfg["token"], cfg["api_url"])
1972
+ total_events += n
1973
+ date = datetime.fromtimestamp(s["created_at"]).strftime("%Y-%m-%d") if s.get("created_at") else "?"
1974
+ print(f" {s['id'][:8]} {date} {n:5} events resynced")
1975
+ except SystemExit as exc:
1976
+ print(f" {s['id'][:8]} FAILED: {exc}")
1977
+ print(f"\nDone. {total_events} events resynced across {len(candidates)} sessions.")
1978
+
1979
+
1932
1980
  def cmd_tag(args: argparse.Namespace) -> None:
1933
1981
  session = _resolve_session(args.session_id)
1934
1982
  tags = [t.strip() for t in args.tags.split(",") if t.strip()]
@@ -2356,6 +2404,10 @@ def main() -> None:
2356
2404
  pu.add_argument("session_id", nargs="?")
2357
2405
  pu.add_argument("--force", "-f", action="store_true", help="Re-upload all events (replaces previous push)")
2358
2406
  pu.add_argument("--local", action="store_true", help="Push to local dev API (localhost:8000)")
2407
+ rs_ev = sub.add_parser("resync", help="Re-push already-synced sessions to decrypt sensitive fields server-side")
2408
+ rs_ev.add_argument("session_id", nargs="?", help="Session ID prefix (default: all synced)")
2409
+ rs_ev.add_argument("--since", help="Only resync sessions started on or after this date (YYYY-MM-DD)")
2410
+ rs_ev.add_argument("--local", action="store_true", help="Resync against local dev API")
2359
2411
  tg = sub.add_parser("tag", help="Tag a session")
2360
2412
  tg.add_argument("session_id", help="Session ID (prefix ok)")
2361
2413
  tg.add_argument("tags", help="Comma-separated tags")
@@ -2422,7 +2474,7 @@ def main() -> None:
2422
2474
  "init": cmd_init, "start": cmd_start, "stop": cmd_stop, "connect": cmd_connect,
2423
2475
  "view": cmd_view, "log": cmd_log, "status": cmd_status,
2424
2476
  "login": cmd_login, "logout": cmd_logout, "accounts": cmd_accounts, "switch": cmd_switch,
2425
- "push": cmd_push, "tag": cmd_tag, "publish": cmd_publish,
2477
+ "push": cmd_push, "resync": cmd_resync, "tag": cmd_tag, "publish": cmd_publish,
2426
2478
  "delete": cmd_delete, "review": cmd_review, "consent": cmd_consent,
2427
2479
  "update": cmd_update, "lock": cmd_lock, "reset": cmd_reset, "uninstall": cmd_uninstall,
2428
2480
  "extension": cmd_extension,
@@ -146,6 +146,7 @@ _META_EXTRACTORS = {
146
146
  "SubagentStop": lambda d: {
147
147
  "tool": _TOOL, "agent_type": d.get("agent_type", "unknown"), "agent_id": d.get("agent_id", ""),
148
148
  "last_assistant_message": d.get("last_assistant_message", ""),
149
+ "last_message_preview": (d.get("last_assistant_message") or "")[:200],
149
150
  },
150
151
  "TaskCreated": lambda d: {"tool": _TOOL, "task_id": d.get("task_id", ""), "subject": d.get("task_subject", "")},
151
152
  "TaskCompleted": lambda d: {"tool": _TOOL, "task_id": d.get("task_id", "")},
@@ -9,6 +9,34 @@ from typing import Any
9
9
  from methodproof import store
10
10
 
11
11
 
12
+ def _prepare_events_for_push(session_id: str) -> list[dict[str, Any]]:
13
+ """Return events for outbound sync. Sensitive fields encrypted with the
14
+ local master-derived db_key are decrypted here so the server receives
15
+ plaintext. Users who opt into true E2E (`mp e2e on`) keep ciphertext
16
+ verbatim — they explicitly chose platform-unreadable storage.
17
+
18
+ Local-DB at-rest encryption and E2E share the `e2e:v1:` wire format,
19
+ so without this step the server can't tell them apart and shows
20
+ ciphertext for normal (non-E2E) users."""
21
+ raw = store.get_events(session_id, decrypt=False)
22
+ from methodproof import config
23
+ cfg = config.load()
24
+ if cfg.get("e2e_mode"):
25
+ return raw
26
+ key = store._try_load_db_key()
27
+ if key is None:
28
+ return raw
29
+ from methodproof.crypto import decrypt_metadata_safe
30
+ prepared = []
31
+ for e in raw:
32
+ meta = json.loads(e["metadata"])
33
+ decrypt_metadata_safe(meta, key)
34
+ e2 = dict(e)
35
+ e2["metadata"] = json.dumps(meta)
36
+ prepared.append(e2)
37
+ return prepared
38
+
39
+
12
40
  def sync_metadata(session: dict[str, Any], token: str, api_url: str) -> None:
13
41
  """Sync repo, tags, and visibility for an already-pushed session."""
14
42
  remote_id = session.get("remote_id")
@@ -117,13 +145,17 @@ def push(session_id: str, token: str, api_url: str, force: bool = False) -> str:
117
145
  create_body["session_binding"] = session["session_binding"]
118
146
  if session.get("device_id"):
119
147
  create_body["device_id"] = session["device_id"]
148
+ # Send the original capture start so the remote session's duration reflects
149
+ # the actual session, not the push transaction.
150
+ if session.get("created_at"):
151
+ create_body["started_at"] = _iso(session["created_at"])
120
152
  result = _request("POST", "/personal/sessions", api_url, token,
121
153
  create_body or None)
122
154
  remote_id = result["session_id"]
123
155
  print(f"done ({remote_id[:8]})")
124
156
 
125
157
  # Upload events in batches (with hash chain if available)
126
- events = store.get_events(session_id)
158
+ events = _prepare_events_for_push(session_id)
127
159
  event_hashes = store.get_event_hashes(session_id)
128
160
  hash_lookup = {h["event_id"]: h["hash"] for h in event_hashes}
129
161
  total = len(events)
@@ -190,7 +222,11 @@ def push(session_id: str, token: str, api_url: str, force: bool = False) -> str:
190
222
  pass # cryptography not installed
191
223
 
192
224
  # Complete (server drains ingest queue + materializes stats — needs longer timeout)
193
- _request("PUT", f"/personal/sessions/{remote_id}/complete", api_url, token, timeout=90)
225
+ complete_body: dict[str, Any] | None = None
226
+ if session.get("completed_at"):
227
+ complete_body = {"ended_at": _iso(session["completed_at"])}
228
+ _request("PUT", f"/personal/sessions/{remote_id}/complete", api_url, token,
229
+ complete_body, timeout=90)
194
230
  store.mark_synced(session_id, remote_id)
195
231
 
196
232
  # Sync metadata (repo, tags, visibility)
@@ -206,6 +242,34 @@ def _iso(ts: float) -> str:
206
242
  return datetime.fromtimestamp(ts, tz=UTC).isoformat()
207
243
 
208
244
 
245
+ def resync_events(session: dict[str, Any], token: str, api_url: str) -> int:
246
+ """Re-push events for an already-synced session so server metadata reflects
247
+ the current local state. Uses the dedicated `/events/resync` endpoint
248
+ (accepts completed sessions; updates existing Action nodes in place).
249
+ Returns the number of events submitted."""
250
+ remote_id = session.get("remote_id")
251
+ if not remote_id:
252
+ raise SystemExit(f"Session {session['id'][:8]} has no remote_id — push it first.")
253
+ events = _prepare_events_for_push(session["id"])
254
+ total = len(events)
255
+ batch_size = 500
256
+ for i in range(0, total, batch_size):
257
+ batch = events[i:i + batch_size]
258
+ payload = [{"id": e["id"], "metadata": json.loads(e["metadata"])} for e in batch]
259
+ for attempt in range(5):
260
+ try:
261
+ _request("POST", f"/personal/sessions/{remote_id}/events/resync",
262
+ api_url, token, {"events": payload})
263
+ break
264
+ except SystemExit as exc:
265
+ if "429" in str(exc) and attempt < 4:
266
+ import time
267
+ time.sleep(10 * (attempt + 1))
268
+ else:
269
+ raise
270
+ return total
271
+
272
+
209
273
  def sync_research_consent(token: str, api_url: str) -> None:
210
274
  """Sync research consent between CLI (cache) and platform (source of truth)."""
211
275
  from methodproof import config
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "methodproof"
3
- version = "0.8.5"
3
+ version = "0.8.7"
4
4
  description = "See how you code. Capture and visualize your engineering process."
5
5
  requires-python = ">=3.12"
6
6
  dependencies = ["watchdog>=4.0", "websocket-client>=1.7", "cryptography>=46.0.7", "keyring>=25.0", "textual>=0.59", "rich>=13.7", "sqlcipher3>=0.6"]
@@ -708,7 +708,7 @@ wheels = [
708
708
 
709
709
  [[package]]
710
710
  name = "methodproof"
711
- version = "0.8.5"
711
+ version = "0.8.7"
712
712
  source = { editable = "." }
713
713
  dependencies = [
714
714
  { name = "cryptography" },
File without changes
File without changes
File without changes