methodproof 0.8.6__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.
- {methodproof-0.8.6 → methodproof-0.8.7}/CHANGELOG.md +5 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/PKG-INFO +1 -1
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/cli.py +53 -1
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/hooks/claude_code.py +1 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/sync.py +57 -1
- {methodproof-0.8.6 → methodproof-0.8.7}/pyproject.toml +1 -1
- {methodproof-0.8.6 → methodproof-0.8.7}/uv.lock +1 -1
- {methodproof-0.8.6 → methodproof-0.8.7}/.github/workflows/ci.yml +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/.gitignore +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/LICENSE +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/README.md +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/__init__.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/__main__.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/_daemon.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/agents/__init__.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/agents/base.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/agents/music.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/agents/terminal.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/agents/watcher.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/analysis.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/binding.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/bip39.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/bridge.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/config.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/crypto.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/e2e.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/graph.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/hook.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/hooks/__init__.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/hooks/claude_code.sh +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/hooks/cline_hook.sh +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/hooks/codex_hook.sh +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/hooks/gemini_hook.sh +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/hooks/install.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/hooks/kiro_hook.sh +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/hooks/mcp_register.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/hooks/model_cache.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/hooks/openclaw/HOOK.md +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/hooks/openclaw/handler.ts +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/hooks/openclaw_install.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/hooks/opencode_plugin.js +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/hooks/wrappers.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/integrity.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/kdf.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/keychain.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/live.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/lock.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/mcp.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/migrate_db.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/proxy.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/proxy_daemon.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/repos.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/skills/methodproof/SKILL.md +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/store.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/tui/__init__.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/tui/consent.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/tui/init.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/tui/log.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/tui/login_success.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/tui/review.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/tui/start.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/tui/status.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/tui/theme.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/viewer.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/methodproof/wordlist.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/test_windows_compat.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/__init__.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/conftest.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_analysis.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_cli_auth.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_cli_config.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_cli_helpers.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_cli_session.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_cli_share.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_cli_start.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_cli_update.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_e2e_integration.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_graph.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_hooks.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_live.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_model_cache.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_openclaw_hooks.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_profiles.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_repos.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_security.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_store.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_sync.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_viewer.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_watcher_ignore.py +0 -0
- {methodproof-0.8.6 → methodproof-0.8.7}/tests/test_wrappers.py +0 -0
|
@@ -1,5 +1,10 @@
|
|
|
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
|
+
|
|
3
8
|
## [0.8.6] — 2026-04-24
|
|
4
9
|
|
|
5
10
|
### Fixed
|
|
@@ -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")
|
|
@@ -127,7 +155,7 @@ def push(session_id: str, token: str, api_url: str, force: bool = False) -> str:
|
|
|
127
155
|
print(f"done ({remote_id[:8]})")
|
|
128
156
|
|
|
129
157
|
# Upload events in batches (with hash chain if available)
|
|
130
|
-
events =
|
|
158
|
+
events = _prepare_events_for_push(session_id)
|
|
131
159
|
event_hashes = store.get_event_hashes(session_id)
|
|
132
160
|
hash_lookup = {h["event_id"]: h["hash"] for h in event_hashes}
|
|
133
161
|
total = len(events)
|
|
@@ -214,6 +242,34 @@ def _iso(ts: float) -> str:
|
|
|
214
242
|
return datetime.fromtimestamp(ts, tz=UTC).isoformat()
|
|
215
243
|
|
|
216
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
|
+
|
|
217
273
|
def sync_research_consent(token: str, api_url: str) -> None:
|
|
218
274
|
"""Sync research consent between CLI (cache) and platform (source of truth)."""
|
|
219
275
|
from methodproof import config
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "methodproof"
|
|
3
|
-
version = "0.8.
|
|
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"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|