methodproof 0.8.1__tar.gz → 0.8.2__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 (89) hide show
  1. {methodproof-0.8.1 → methodproof-0.8.2}/PKG-INFO +1 -1
  2. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/graph.py +88 -41
  3. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/hooks/claude_code.py +8 -0
  4. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/hooks/claude_code.sh +4 -0
  5. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/hooks/install.py +1 -0
  6. {methodproof-0.8.1 → methodproof-0.8.2}/pyproject.toml +1 -1
  7. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_graph.py +5 -4
  8. methodproof-0.8.1/.code-review-graph/.gitignore +0 -3
  9. methodproof-0.8.1/.code-review-graph/graph.db +0 -0
  10. {methodproof-0.8.1 → methodproof-0.8.2}/.github/workflows/ci.yml +0 -0
  11. {methodproof-0.8.1 → methodproof-0.8.2}/.gitignore +0 -0
  12. {methodproof-0.8.1 → methodproof-0.8.2}/CHANGELOG.md +0 -0
  13. {methodproof-0.8.1 → methodproof-0.8.2}/LICENSE +0 -0
  14. {methodproof-0.8.1 → methodproof-0.8.2}/README.md +0 -0
  15. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/__init__.py +0 -0
  16. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/__main__.py +0 -0
  17. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/_daemon.py +0 -0
  18. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/agents/__init__.py +0 -0
  19. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/agents/base.py +0 -0
  20. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/agents/music.py +0 -0
  21. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/agents/terminal.py +0 -0
  22. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/agents/watcher.py +0 -0
  23. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/analysis.py +0 -0
  24. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/binding.py +0 -0
  25. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/bip39.py +0 -0
  26. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/bridge.py +0 -0
  27. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/cli.py +0 -0
  28. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/config.py +0 -0
  29. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/crypto.py +0 -0
  30. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/e2e.py +0 -0
  31. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/hook.py +0 -0
  32. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/hooks/__init__.py +0 -0
  33. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/hooks/cline_hook.sh +0 -0
  34. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/hooks/codex_hook.sh +0 -0
  35. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/hooks/gemini_hook.sh +0 -0
  36. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/hooks/kiro_hook.sh +0 -0
  37. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/hooks/mcp_register.py +0 -0
  38. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/hooks/openclaw/HOOK.md +0 -0
  39. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/hooks/openclaw/handler.ts +0 -0
  40. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/hooks/openclaw_install.py +0 -0
  41. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/hooks/opencode_plugin.js +0 -0
  42. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/hooks/wrappers.py +0 -0
  43. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/integrity.py +0 -0
  44. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/kdf.py +0 -0
  45. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/keychain.py +0 -0
  46. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/live.py +0 -0
  47. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/lock.py +0 -0
  48. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/mcp.py +0 -0
  49. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/migrate_db.py +0 -0
  50. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/proxy.py +0 -0
  51. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/proxy_daemon.py +0 -0
  52. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/repos.py +0 -0
  53. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/skills/methodproof/SKILL.md +0 -0
  54. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/store.py +0 -0
  55. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/sync.py +0 -0
  56. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/tui/__init__.py +0 -0
  57. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/tui/consent.py +0 -0
  58. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/tui/init.py +0 -0
  59. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/tui/log.py +0 -0
  60. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/tui/login_success.py +0 -0
  61. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/tui/review.py +0 -0
  62. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/tui/start.py +0 -0
  63. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/tui/status.py +0 -0
  64. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/tui/theme.py +0 -0
  65. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/viewer.py +0 -0
  66. {methodproof-0.8.1 → methodproof-0.8.2}/methodproof/wordlist.py +0 -0
  67. {methodproof-0.8.1 → methodproof-0.8.2}/test_windows_compat.py +0 -0
  68. {methodproof-0.8.1 → methodproof-0.8.2}/tests/__init__.py +0 -0
  69. {methodproof-0.8.1 → methodproof-0.8.2}/tests/conftest.py +0 -0
  70. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_analysis.py +0 -0
  71. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_cli_auth.py +0 -0
  72. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_cli_config.py +0 -0
  73. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_cli_helpers.py +0 -0
  74. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_cli_session.py +0 -0
  75. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_cli_share.py +0 -0
  76. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_cli_start.py +0 -0
  77. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_cli_update.py +0 -0
  78. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_e2e_integration.py +0 -0
  79. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_hooks.py +0 -0
  80. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_live.py +0 -0
  81. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_openclaw_hooks.py +0 -0
  82. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_profiles.py +0 -0
  83. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_repos.py +0 -0
  84. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_security.py +0 -0
  85. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_store.py +0 -0
  86. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_sync.py +0 -0
  87. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_viewer.py +0 -0
  88. {methodproof-0.8.1 → methodproof-0.8.2}/tests/test_wrappers.py +0 -0
  89. {methodproof-0.8.1 → methodproof-0.8.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: methodproof
3
- Version: 0.8.1
3
+ Version: 0.8.2
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
@@ -36,20 +36,19 @@ def build(session_id: str) -> dict[str, int]:
36
36
  db.execute(
37
37
  "DELETE FROM causal_links WHERE source_id IN "
38
38
  "(SELECT id FROM events WHERE session_id = ?)", (session_id,))
39
- stats["causal"] += _link(db, session_id, "llm_prompt", "llm_completion",
40
- "RECEIVED", 60, match_model=True)
41
- stats["causal"] += _link(db, session_id, "llm_completion", "file_edit",
42
- "INFORMED", 60)
43
- stats["causal"] += _link(db, session_id, "web_search", "web_visit",
44
- "LED_TO", 120)
45
- stats["causal"] += _link(db, session_id, "browser_search", "browser_visit",
46
- "LED_TO", 120)
39
+ stats["causal"] += _link_nearest(db, session_id, "llm_prompt",
40
+ "llm_completion", "RECEIVED", "model")
41
+ stats["causal"] += _link_until_next(db, session_id, "llm_completion",
42
+ "file_edit", "INFORMED")
43
+ stats["causal"] += _link_until_next(db, session_id, "web_search",
44
+ "web_visit", "LED_TO")
45
+ stats["causal"] += _link_until_next(db, session_id, "browser_search",
46
+ "browser_visit", "LED_TO")
47
47
  stats["causal"] += _link_pasted(db, session_id)
48
- # Agent causal links (OpenClaw / agent gateways)
49
- stats["causal"] += _link(db, session_id, "agent_prompt", "agent_completion",
50
- "RECEIVED", 120, match_model=True)
51
- stats["causal"] += _link(db, session_id, "agent_completion", "file_edit",
52
- "INFORMED", 60)
48
+ stats["causal"] += _link_nearest(db, session_id, "agent_prompt",
49
+ "agent_completion", "RECEIVED", "model")
50
+ stats["causal"] += _link_until_next(db, session_id, "agent_completion",
51
+ "file_edit", "INFORMED")
53
52
 
54
53
  # Resources
55
54
  for e in events:
@@ -95,39 +94,87 @@ def build(session_id: str) -> dict[str, int]:
95
94
  return stats
96
95
 
97
96
 
98
- def _link(
97
+ def _link_nearest(
99
98
  db: object, sid: str, src_type: str, tgt_type: str,
100
- rel: str, window_sec: int, match_model: bool = False,
99
+ rel: str, match_field: str | None = None,
101
100
  ) -> int:
102
- model_clause = (
103
- "AND json_extract(mp_json(s.metadata), '$.model') = json_extract(mp_json(t.metadata), '$.model')"
104
- if match_model else ""
105
- )
106
- sql = f"""
107
- INSERT OR IGNORE INTO causal_links (source_id, target_id, type)
108
- SELECT s.id, t.id, ?
109
- FROM events s JOIN events t ON t.session_id = s.session_id
110
- WHERE s.session_id = ? AND s.type = ? AND t.type = ?
111
- AND t.timestamp > s.timestamp
112
- AND (t.timestamp - s.timestamp) <= ?
113
- {model_clause}
114
- """
115
- return db.execute(sql, (rel, sid, src_type, tgt_type, window_sec)).rowcount
101
+ """1:1 nearest-neighbor pairing. No time window."""
102
+ rows = db.execute(
103
+ "SELECT id, type, timestamp, metadata FROM events "
104
+ "WHERE session_id = ? AND type IN (?, ?) ORDER BY timestamp",
105
+ (sid, src_type, tgt_type),
106
+ ).fetchall()
107
+ claimed: set[str] = set()
108
+ pairs: list[tuple[str, str, str]] = []
109
+ for src in rows:
110
+ if src["type"] != src_type:
111
+ continue
112
+ src_val = _decompress_meta(src["metadata"]).get(match_field) if match_field else None
113
+ for tgt in rows:
114
+ if tgt["type"] != tgt_type or tgt["id"] in claimed or tgt["timestamp"] <= src["timestamp"]:
115
+ continue
116
+ if match_field and _decompress_meta(tgt["metadata"]).get(match_field) != src_val:
117
+ continue
118
+ pairs.append((src["id"], tgt["id"], rel))
119
+ claimed.add(tgt["id"])
120
+ break
121
+ if pairs:
122
+ db.executemany(
123
+ "INSERT OR IGNORE INTO causal_links (source_id, target_id, type) "
124
+ "VALUES (?, ?, ?)", pairs)
125
+ return len(pairs)
126
+
127
+
128
+ def _link_until_next(
129
+ db: object, sid: str, src_type: str, tgt_type: str, rel: str,
130
+ ) -> int:
131
+ """Link source to all targets until the next source event."""
132
+ rows = db.execute(
133
+ "SELECT id, type FROM events "
134
+ "WHERE session_id = ? AND type IN (?, ?) ORDER BY timestamp",
135
+ (sid, src_type, tgt_type),
136
+ ).fetchall()
137
+ pairs: list[tuple[str, str, str]] = []
138
+ current_src: str | None = None
139
+ for ev in rows:
140
+ if ev["type"] == src_type:
141
+ current_src = ev["id"]
142
+ elif current_src:
143
+ pairs.append((current_src, ev["id"], rel))
144
+ if pairs:
145
+ db.executemany(
146
+ "INSERT OR IGNORE INTO causal_links (source_id, target_id, type) "
147
+ "VALUES (?, ?, ?)", pairs)
148
+ return len(pairs)
116
149
 
117
150
 
118
151
  def _link_pasted(db: object, sid: str) -> int:
119
- """browser_copy → file_edit: ≤30s, content length within 20%."""
120
- sql = """
121
- INSERT OR IGNORE INTO causal_links (source_id, target_id, type, confidence)
122
- SELECT s.id, t.id, 'PASTED_FROM', 0.7
123
- FROM events s JOIN events t ON t.session_id = s.session_id
124
- WHERE s.session_id = ? AND s.type = 'browser_copy' AND t.type = 'file_edit'
125
- AND t.timestamp > s.timestamp AND (t.timestamp - s.timestamp) <= 30
126
- AND abs(json_extract(mp_json(t.metadata), '$.lines_added') * 40.0
127
- - json_extract(mp_json(s.metadata), '$.text_length'))
128
- < json_extract(mp_json(s.metadata), '$.text_length') * 0.2
129
- """
130
- return db.execute(sql, (sid,)).rowcount
152
+ """browser_copy → nearest file_edit with content length within 20%."""
153
+ rows = db.execute(
154
+ "SELECT id, type, timestamp, metadata FROM events "
155
+ "WHERE session_id = ? AND type IN ('browser_copy', 'file_edit') "
156
+ "ORDER BY timestamp", (sid,),
157
+ ).fetchall()
158
+ claimed: set[str] = set()
159
+ pairs: list[tuple[str, str]] = []
160
+ for src in rows:
161
+ if src["type"] != "browser_copy":
162
+ continue
163
+ src_len = _decompress_meta(src["metadata"]).get("text_length", 0)
164
+ if not src_len:
165
+ continue
166
+ for tgt in rows:
167
+ if tgt["type"] != "file_edit" or tgt["id"] in claimed or tgt["timestamp"] <= src["timestamp"]:
168
+ continue
169
+ if abs(_decompress_meta(tgt["metadata"]).get("lines_added", 0) * 40 - src_len) < src_len * 0.2:
170
+ pairs.append((src["id"], tgt["id"]))
171
+ claimed.add(tgt["id"])
172
+ break
173
+ if pairs:
174
+ db.executemany(
175
+ "INSERT OR IGNORE INTO causal_links (source_id, target_id, type, confidence) "
176
+ "VALUES (?, ?, 'PASTED_FROM', 0.7)", pairs)
177
+ return len(pairs)
131
178
 
132
179
 
133
180
  def _link_action_resources(db: object, sid: str) -> None:
@@ -76,6 +76,8 @@ _TYPE_MAP = {
76
76
  # Worktree
77
77
  "WorktreeCreate": "worktree_create",
78
78
  "WorktreeRemove": "worktree_remove",
79
+ # Notifications
80
+ "Notification": "notification",
79
81
  }
80
82
 
81
83
  _TOOL = "claude_code"
@@ -139,6 +141,12 @@ _META_EXTRACTORS = {
139
141
  "ElicitationResult": lambda d: {"tool": _TOOL},
140
142
  "WorktreeCreate": lambda d: {"tool": _TOOL, "worktree_path": d.get("worktree_path", "")},
141
143
  "WorktreeRemove": lambda d: {"tool": _TOOL, "worktree_path": d.get("worktree_path", "")},
144
+ "Notification": lambda d: {
145
+ "tool": _TOOL,
146
+ "title": d.get("title", ""),
147
+ "message": d.get("message", d.get("text", ""))[:1000],
148
+ "notification_type": d.get("type", d.get("notification_type", "")),
149
+ },
142
150
  }
143
151
 
144
152
 
@@ -162,6 +162,10 @@ if command -v jq >/dev/null 2>&1; then
162
162
  TYPE=$(echo "$EVENT" | sed 's/Elicitation$/mcp_elicitation/;s/ElicitationResult/mcp_elicitation_result/')
163
163
  META='{"tool":"claude_code"}'
164
164
  ;;
165
+ Notification)
166
+ TYPE="notification"
167
+ META=$(echo "$INPUT" | jq -c '{tool: "claude_code", title: (.title // ""), message: ((.message // .text // "")[:1000]), notification_type: (.type // .notification_type // "")}' 2>/dev/null || echo '{"tool":"claude_code"}')
168
+ ;;
165
169
  *)
166
170
  TYPE="claude_code_event"
167
171
  META="{\"event\":\"$EVENT\"}"
@@ -38,6 +38,7 @@ HOOK_EVENTS = [
38
38
  "ElicitationResult", # user responded to MCP
39
39
  "WorktreeCreate", # git worktree created (parallel agent)
40
40
  "WorktreeRemove", # git worktree removed
41
+ "Notification", # system notifications (recap, compaction summary, etc.)
41
42
  ]
42
43
 
43
44
  # --- Codex CLI ---
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "methodproof"
3
- version = "0.8.1"
3
+ version = "0.8.2"
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", "sqlcipher3>=0.6"]
@@ -93,15 +93,16 @@ def test_led_to_link() -> None:
93
93
  assert db.execute("SELECT count(*) FROM causal_links WHERE type='LED_TO'").fetchone()[0] == 1
94
94
 
95
95
 
96
- def test_no_link_outside_window() -> None:
97
- """completion edit >60s apart no INFORMED."""
96
+ def test_informed_bounded_by_next_completion() -> None:
97
+ """Edit after a second completion links only to the second."""
98
98
  sid = _session([
99
99
  _event("llm_completion", 100, {"model": "claude"}),
100
- _event("file_edit", 200, {"path": "a.py"}), # 100s gap
100
+ _event("llm_completion", 200, {"model": "claude"}),
101
+ _event("file_edit", 250, {"path": "a.py"}),
101
102
  ])
102
103
  graph.build(sid)
103
104
  db = store._db()
104
- assert db.execute("SELECT count(*) FROM causal_links WHERE type='INFORMED'").fetchone()[0] == 0
105
+ assert db.execute("SELECT count(*) FROM causal_links WHERE type='INFORMED'").fetchone()[0] == 1
105
106
 
106
107
 
107
108
  def test_resource_creation() -> None:
@@ -1,3 +0,0 @@
1
- # Auto-generated by code-review-graph — do not commit database files.
2
- # The graph.db contains absolute paths and code structure metadata.
3
- *
File without changes
File without changes
File without changes
File without changes
File without changes