methodproof 0.8.1__tar.gz → 0.8.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 (89) hide show
  1. {methodproof-0.8.1 → methodproof-0.8.3}/PKG-INFO +1 -1
  2. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/graph.py +88 -41
  3. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/hooks/claude_code.py +40 -1
  4. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/hooks/claude_code.sh +14 -1
  5. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/hooks/install.py +1 -0
  6. {methodproof-0.8.1 → methodproof-0.8.3}/pyproject.toml +1 -1
  7. {methodproof-0.8.1 → methodproof-0.8.3}/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.3}/.github/workflows/ci.yml +0 -0
  11. {methodproof-0.8.1 → methodproof-0.8.3}/.gitignore +0 -0
  12. {methodproof-0.8.1 → methodproof-0.8.3}/CHANGELOG.md +0 -0
  13. {methodproof-0.8.1 → methodproof-0.8.3}/LICENSE +0 -0
  14. {methodproof-0.8.1 → methodproof-0.8.3}/README.md +0 -0
  15. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/__init__.py +0 -0
  16. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/__main__.py +0 -0
  17. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/_daemon.py +0 -0
  18. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/agents/__init__.py +0 -0
  19. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/agents/base.py +0 -0
  20. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/agents/music.py +0 -0
  21. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/agents/terminal.py +0 -0
  22. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/agents/watcher.py +0 -0
  23. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/analysis.py +0 -0
  24. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/binding.py +0 -0
  25. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/bip39.py +0 -0
  26. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/bridge.py +0 -0
  27. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/cli.py +0 -0
  28. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/config.py +0 -0
  29. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/crypto.py +0 -0
  30. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/e2e.py +0 -0
  31. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/hook.py +0 -0
  32. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/hooks/__init__.py +0 -0
  33. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/hooks/cline_hook.sh +0 -0
  34. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/hooks/codex_hook.sh +0 -0
  35. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/hooks/gemini_hook.sh +0 -0
  36. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/hooks/kiro_hook.sh +0 -0
  37. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/hooks/mcp_register.py +0 -0
  38. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/hooks/openclaw/HOOK.md +0 -0
  39. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/hooks/openclaw/handler.ts +0 -0
  40. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/hooks/openclaw_install.py +0 -0
  41. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/hooks/opencode_plugin.js +0 -0
  42. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/hooks/wrappers.py +0 -0
  43. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/integrity.py +0 -0
  44. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/kdf.py +0 -0
  45. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/keychain.py +0 -0
  46. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/live.py +0 -0
  47. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/lock.py +0 -0
  48. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/mcp.py +0 -0
  49. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/migrate_db.py +0 -0
  50. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/proxy.py +0 -0
  51. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/proxy_daemon.py +0 -0
  52. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/repos.py +0 -0
  53. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/skills/methodproof/SKILL.md +0 -0
  54. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/store.py +0 -0
  55. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/sync.py +0 -0
  56. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/tui/__init__.py +0 -0
  57. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/tui/consent.py +0 -0
  58. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/tui/init.py +0 -0
  59. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/tui/log.py +0 -0
  60. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/tui/login_success.py +0 -0
  61. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/tui/review.py +0 -0
  62. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/tui/start.py +0 -0
  63. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/tui/status.py +0 -0
  64. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/tui/theme.py +0 -0
  65. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/viewer.py +0 -0
  66. {methodproof-0.8.1 → methodproof-0.8.3}/methodproof/wordlist.py +0 -0
  67. {methodproof-0.8.1 → methodproof-0.8.3}/test_windows_compat.py +0 -0
  68. {methodproof-0.8.1 → methodproof-0.8.3}/tests/__init__.py +0 -0
  69. {methodproof-0.8.1 → methodproof-0.8.3}/tests/conftest.py +0 -0
  70. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_analysis.py +0 -0
  71. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_cli_auth.py +0 -0
  72. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_cli_config.py +0 -0
  73. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_cli_helpers.py +0 -0
  74. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_cli_session.py +0 -0
  75. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_cli_share.py +0 -0
  76. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_cli_start.py +0 -0
  77. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_cli_update.py +0 -0
  78. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_e2e_integration.py +0 -0
  79. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_hooks.py +0 -0
  80. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_live.py +0 -0
  81. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_openclaw_hooks.py +0 -0
  82. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_profiles.py +0 -0
  83. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_repos.py +0 -0
  84. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_security.py +0 -0
  85. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_store.py +0 -0
  86. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_sync.py +0 -0
  87. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_viewer.py +0 -0
  88. {methodproof-0.8.1 → methodproof-0.8.3}/tests/test_wrappers.py +0 -0
  89. {methodproof-0.8.1 → methodproof-0.8.3}/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.3
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,9 +141,33 @@ _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
 
153
+ def _extract_recap(transcript_path: str) -> str | None:
154
+ """Grep transcript for the last ※ recap: line. Journal mode only."""
155
+ import pathlib
156
+ tp = pathlib.Path(transcript_path)
157
+ if not tp.exists():
158
+ return None
159
+ try:
160
+ text = tp.read_text(errors="replace")
161
+ except OSError:
162
+ return None
163
+ last_recap = None
164
+ for line in text.splitlines():
165
+ stripped = line.strip()
166
+ if stripped.startswith("※ recap:") or stripped.startswith("recap:"):
167
+ last_recap = stripped.removeprefix("※ ").removeprefix("recap:").strip()
168
+ return last_recap
169
+
170
+
145
171
  def main() -> None:
146
172
  try:
147
173
  data = json.load(sys.stdin)
@@ -154,7 +180,20 @@ def main() -> None:
154
180
  meta = extractor(data) if extractor else {"tool": _TOOL, "event": event}
155
181
  ts = time.time()
156
182
 
157
- payload = json.dumps({"events": [{"type": etype, "timestamp": ts, "metadata": meta}]}).encode()
183
+ events_out = [{"type": etype, "timestamp": ts, "metadata": meta}]
184
+
185
+ # On Stop, grep transcript for recap (journal mode only)
186
+ transcript_path = data.get("transcript_path", "")
187
+ if event == "Stop" and transcript_path:
188
+ recap = _extract_recap(transcript_path)
189
+ if recap:
190
+ events_out.append({
191
+ "type": "context_recap",
192
+ "timestamp": ts,
193
+ "metadata": {"tool": _TOOL, "recap": recap[:2000]},
194
+ })
195
+
196
+ payload = json.dumps({"events": events_out}).encode()
158
197
  req = urllib.request.Request(
159
198
  "http://localhost:9877/events", data=payload,
160
199
  headers={"Content-Type": "application/json"}, method="POST",
@@ -125,6 +125,15 @@ if command -v jq >/dev/null 2>&1; then
125
125
  Stop)
126
126
  TYPE="agent_turn_end"
127
127
  META='{"tool":"claude_code"}'
128
+ # Extract recap from transcript if available (journal mode)
129
+ TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcript_path // empty' 2>/dev/null)
130
+ if [ -n "$TRANSCRIPT" ] && [ -f "$TRANSCRIPT" ]; then
131
+ RECAP=$(grep -E '※ recap:|^recap:' "$TRANSCRIPT" | tail -1 | sed 's/^.*※ recap: *//;s/^recap: *//' | head -c 2000)
132
+ if [ -n "$RECAP" ]; then
133
+ RECAP_ESCAPED=$(echo "$RECAP" | jq -Rs '.' 2>/dev/null || echo '""')
134
+ RECAP_EVENT=",{\"type\":\"context_recap\",\"timestamp\":$TS,\"metadata\":{\"tool\":\"claude_code\",\"recap\":$RECAP_ESCAPED}}"
135
+ fi
136
+ fi
128
137
  ;;
129
138
  StopFailure)
130
139
  TYPE="agent_turn_error"
@@ -162,6 +171,10 @@ if command -v jq >/dev/null 2>&1; then
162
171
  TYPE=$(echo "$EVENT" | sed 's/Elicitation$/mcp_elicitation/;s/ElicitationResult/mcp_elicitation_result/')
163
172
  META='{"tool":"claude_code"}'
164
173
  ;;
174
+ Notification)
175
+ TYPE="notification"
176
+ 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"}')
177
+ ;;
165
178
  *)
166
179
  TYPE="claude_code_event"
167
180
  META="{\"event\":\"$EVENT\"}"
@@ -174,7 +187,7 @@ else
174
187
  fi
175
188
 
176
189
  # Post to bridge with strict 1-second timeout (never block Claude Code)
177
- PAYLOAD="{\"events\":[{\"type\":\"$TYPE\",\"timestamp\":$TS,\"metadata\":$META}]}"
190
+ PAYLOAD="{\"events\":[{\"type\":\"$TYPE\",\"timestamp\":$TS,\"metadata\":$META}${RECAP_EVENT:-}]}"
178
191
  RESPONSE=$(curl -s -w "\n%{http_code}" --max-time 1 --connect-timeout 0.5 \
179
192
  -X POST http://localhost:9877/events \
180
193
  -H "Content-Type: application/json" \
@@ -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.3"
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