meshcode 2.4.9__tar.gz → 2.5.0__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 (31) hide show
  1. {meshcode-2.4.9 → meshcode-2.5.0}/PKG-INFO +1 -1
  2. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode.egg-info/PKG-INFO +1 -1
  3. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode.egg-info/SOURCES.txt +2 -1
  4. {meshcode-2.4.9 → meshcode-2.5.0}/pyproject.toml +1 -1
  5. meshcode-2.5.0/tests/test_status_enum_coverage.py +199 -0
  6. {meshcode-2.4.9 → meshcode-2.5.0}/README.md +0 -0
  7. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/__init__.py +0 -0
  8. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/cli.py +0 -0
  9. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/comms_v4.py +0 -0
  10. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/invites.py +0 -0
  11. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/launcher.py +0 -0
  12. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/launcher_install.py +0 -0
  13. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/meshcode_mcp/__init__.py +0 -0
  14. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/meshcode_mcp/__main__.py +0 -0
  15. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/meshcode_mcp/backend.py +0 -0
  16. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/meshcode_mcp/realtime.py +0 -0
  17. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/meshcode_mcp/server.py +0 -0
  18. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/meshcode_mcp/test_backend.py +0 -0
  19. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  20. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  21. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/preferences.py +0 -0
  22. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/protocol_v2.py +0 -0
  23. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/run_agent.py +0 -0
  24. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/secrets.py +0 -0
  25. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/self_update.py +0 -0
  26. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode/setup_clients.py +0 -0
  27. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode.egg-info/dependency_links.txt +0 -0
  28. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode.egg-info/entry_points.txt +0 -0
  29. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode.egg-info/requires.txt +0 -0
  30. {meshcode-2.4.9 → meshcode-2.5.0}/meshcode.egg-info/top_level.txt +0 -0
  31. {meshcode-2.4.9 → meshcode-2.5.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.4.9
3
+ Version: 2.5.0
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.4.9
3
+ Version: 2.5.0
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -25,4 +25,5 @@ meshcode/meshcode_mcp/realtime.py
25
25
  meshcode/meshcode_mcp/server.py
26
26
  meshcode/meshcode_mcp/test_backend.py
27
27
  meshcode/meshcode_mcp/test_realtime.py
28
- meshcode/meshcode_mcp/test_server_wrapper.py
28
+ meshcode/meshcode_mcp/test_server_wrapper.py
29
+ tests/test_status_enum_coverage.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshcode"
7
- version = "2.4.9"
7
+ version = "2.5.0"
8
8
  description = "Real-time communication between AI agents — Supabase-backed CLI"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -0,0 +1,199 @@
1
+ """
2
+ CI test: verify all SQL functions handle the full agent status enum.
3
+
4
+ Parses migration files to extract the canonical status enum from the
5
+ valid_agent_status CHECK constraint, then scans the latest version of
6
+ each function for hardcoded status IN (...) filters. Functions that
7
+ intentionally filter a subset must be listed in ALLOWED_SUBSETS.
8
+
9
+ If someone adds a new status to the enum without updating all functions,
10
+ this test fails.
11
+ """
12
+
13
+ import re
14
+ import os
15
+ import sys
16
+ from pathlib import Path
17
+ from collections import defaultdict
18
+
19
+ MIGRATIONS_DIR = Path(__file__).parent.parent / "supabase" / "migrations"
20
+
21
+ # Functions that intentionally use a subset of statuses (not a bug).
22
+ # Format: { "function_name": "reason why subset is intentional" }
23
+ ALLOWED_SUBSETS = {
24
+ "mc_get_my_projects_with_counts": "active_agents counts only online/working/idle by design",
25
+ "mc_meshwork_health_score": "health score only counts online/working/idle as active",
26
+ "mc_spectator_dashboard": "spectator view only shows active agents",
27
+ "mc_agents_auto_sleep": "auto-sleep only transitions specific active statuses by design",
28
+ "mc_agent_profile_after_upsert": "trigger only fires on needs_setup insert by design",
29
+ "mc_onboarding_status": "onboarding checks a subset of 'alive' statuses by design",
30
+ "mc_onboarding_status_from_session": "onboarding checks a subset of 'alive' statuses by design",
31
+ }
32
+
33
+ # Functions that operate on TASK status, not agent status.
34
+ # Their status filters (open, in_progress, done, etc.) are task workflow
35
+ # states and should NOT be compared against the agent status enum.
36
+ TASK_FUNCTIONS = {
37
+ "mc_task_claim",
38
+ "mc_task_complete",
39
+ "mc_task_create",
40
+ "mc_task_reject",
41
+ "mc_task_approve",
42
+ }
43
+
44
+ # Functions that MUST handle all statuses (the stale detector class of bugs).
45
+ # If a function is here and uses a hardcoded IN filter, the test FAILS.
46
+ MUST_HANDLE_ALL = {
47
+ "mc_agents_offline_stale": "stale detector must clear leases for ANY status",
48
+ }
49
+
50
+
51
+ def extract_canonical_statuses() -> set[str]:
52
+ """Extract the status enum from the latest valid_agent_status CHECK constraint."""
53
+ statuses = set()
54
+ # Walk migrations in reverse order to find the latest constraint definition
55
+ migration_files = sorted(MIGRATIONS_DIR.glob("*.sql"), reverse=True)
56
+ for f in migration_files:
57
+ content = f.read_text()
58
+ if "valid_agent_status" not in content:
59
+ continue
60
+ # Find ADD CONSTRAINT block with the ARRAY of statuses
61
+ match = re.search(
62
+ r"ADD\s+CONSTRAINT\s+valid_agent_status\s+CHECK\s*\((.*?)\)",
63
+ content,
64
+ re.DOTALL | re.IGNORECASE,
65
+ )
66
+ if match:
67
+ constraint_body = match.group(1)
68
+ statuses = set(re.findall(r"'(\w+)'", constraint_body))
69
+ if statuses:
70
+ return statuses
71
+ return statuses
72
+
73
+
74
+ def find_latest_function_bodies() -> dict[str, str]:
75
+ """
76
+ Parse all migrations to find the latest CREATE OR REPLACE body for
77
+ each function. Returns {function_name: full_body_sql}.
78
+ """
79
+ functions: dict[str, tuple[str, str]] = {} # name -> (migration_file, body)
80
+ migration_files = sorted(MIGRATIONS_DIR.glob("*.sql"))
81
+
82
+ for f in migration_files:
83
+ content = f.read_text()
84
+ # Match CREATE OR REPLACE FUNCTION (public|meshcode).name(...)
85
+ # Capture everything up to the closing $$;
86
+ pattern = re.compile(
87
+ r"CREATE\s+OR\s+REPLACE\s+FUNCTION\s+"
88
+ r"(?:public|meshcode)\.(mc_\w+)\s*\("
89
+ r"(.*?)"
90
+ r"\$\$\s*;",
91
+ re.DOTALL | re.IGNORECASE,
92
+ )
93
+ for match in pattern.finditer(content):
94
+ func_name = match.group(1)
95
+ func_body = match.group(2)
96
+ # Later migrations overwrite earlier ones (sorted order)
97
+ functions[func_name] = (f.name, func_body)
98
+
99
+ return {name: body for name, (_, body) in functions.items()}
100
+
101
+
102
+ def find_status_filters(func_body: str) -> list[set[str]]:
103
+ """
104
+ Find all status IN ('x', 'y', ...) patterns in a function body.
105
+ Returns a list of sets, one per IN clause found.
106
+ """
107
+ filters = []
108
+ # Match: status IN ('x', 'y', ...) or status = 'x'
109
+ in_pattern = re.compile(
110
+ r"\.?status\s+IN\s*\(\s*'(\w+)'(?:\s*,\s*'(\w+)')*\s*\)",
111
+ re.IGNORECASE,
112
+ )
113
+ # More robust: find all IN clauses near "status"
114
+ in_pattern2 = re.compile(
115
+ r"status\s+IN\s*\(([^)]+)\)",
116
+ re.IGNORECASE,
117
+ )
118
+ for match in in_pattern2.finditer(func_body):
119
+ values = set(re.findall(r"'(\w+)'", match.group(1)))
120
+ if values:
121
+ filters.append(values)
122
+
123
+ # Also check for status = 'x' (single value filters)
124
+ eq_pattern = re.compile(r"status\s*=\s*'(\w+)'", re.IGNORECASE)
125
+ for match in eq_pattern.finditer(func_body):
126
+ # Skip SET status = 'x' (those are writes, not filters)
127
+ # Check if preceded by SET or comma (part of UPDATE SET clause)
128
+ start = match.start()
129
+ preceding = func_body[max(0, start - 30):start].lower()
130
+ if "set " in preceding or preceding.rstrip().endswith(","):
131
+ continue
132
+ filters.append({match.group(1)})
133
+
134
+ return filters
135
+
136
+
137
+ def test_status_enum_coverage():
138
+ """Main test: verify no function has a stale status filter."""
139
+ canonical = extract_canonical_statuses()
140
+ assert canonical, (
141
+ "Could not extract canonical status enum from migrations. "
142
+ "Expected valid_agent_status CHECK constraint."
143
+ )
144
+ print(f"Canonical status enum ({len(canonical)} values): {sorted(canonical)}")
145
+
146
+ functions = find_latest_function_bodies()
147
+ assert functions, "No SQL functions found in migrations."
148
+ print(f"Found {len(functions)} functions in migrations.\n")
149
+
150
+ failures = []
151
+
152
+ for func_name, body in sorted(functions.items()):
153
+ # Skip task workflow functions — they use task status, not agent status
154
+ if func_name in TASK_FUNCTIONS:
155
+ continue
156
+
157
+ filters = find_status_filters(body)
158
+ if not filters:
159
+ continue
160
+
161
+ for i, filter_set in enumerate(filters):
162
+ missing = canonical - filter_set
163
+ extra = filter_set - canonical
164
+
165
+ if extra:
166
+ failures.append(
167
+ f" {func_name} filter #{i+1}: uses unknown statuses "
168
+ f"{sorted(extra)} not in enum"
169
+ )
170
+
171
+ if missing and func_name in MUST_HANDLE_ALL:
172
+ failures.append(
173
+ f" {func_name} filter #{i+1}: MUST handle all statuses "
174
+ f"but missing {sorted(missing)}. "
175
+ f"Reason: {MUST_HANDLE_ALL[func_name]}"
176
+ )
177
+ elif missing and func_name not in ALLOWED_SUBSETS:
178
+ failures.append(
179
+ f" {func_name} filter #{i+1}: uses subset "
180
+ f"{sorted(filter_set)}, missing {sorted(missing)}. "
181
+ f"If intentional, add to ALLOWED_SUBSETS in this test."
182
+ )
183
+
184
+ if failures:
185
+ print("FAILURES:")
186
+ for f in failures:
187
+ print(f)
188
+ print(
189
+ f"\nCanonical enum: {sorted(canonical)}\n"
190
+ "Fix: update the function's status filter OR add it to "
191
+ "ALLOWED_SUBSETS with a reason."
192
+ )
193
+ sys.exit(1)
194
+ else:
195
+ print("PASS: All status filters are either complete or allowlisted.")
196
+
197
+
198
+ if __name__ == "__main__":
199
+ test_status_enum_coverage()
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