synix-agent-mesh 0.1.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.
- synix_agent_mesh-0.1.0/.github/prompts/claude_review.md +68 -0
- synix_agent_mesh-0.1.0/.github/prompts/openai_review.md +73 -0
- synix_agent_mesh-0.1.0/.github/scripts/blind_review.py +263 -0
- synix_agent_mesh-0.1.0/.github/workflows/blind-review.yml +87 -0
- synix_agent_mesh-0.1.0/.github/workflows/ci.yml +22 -0
- synix_agent_mesh-0.1.0/.github/workflows/release.yml +53 -0
- synix_agent_mesh-0.1.0/.gitignore +11 -0
- synix_agent_mesh-0.1.0/LICENSE +21 -0
- synix_agent_mesh-0.1.0/PKG-INFO +566 -0
- synix_agent_mesh-0.1.0/README.md +541 -0
- synix_agent_mesh-0.1.0/config.example.toml +54 -0
- synix_agent_mesh-0.1.0/pipeline.py +11 -0
- synix_agent_mesh-0.1.0/pyproject.toml +62 -0
- synix_agent_mesh-0.1.0/src/synix_agent_mesh/__init__.py +1 -0
- synix_agent_mesh-0.1.0/src/synix_agent_mesh/cli.py +669 -0
- synix_agent_mesh-0.1.0/src/synix_agent_mesh/config.py +177 -0
- synix_agent_mesh-0.1.0/src/synix_agent_mesh/pipeline.py +411 -0
- synix_agent_mesh-0.1.0/src/synix_agent_mesh/server.py +220 -0
- synix_agent_mesh-0.1.0/tests/test_cli.py +140 -0
- synix_agent_mesh-0.1.0/tests/test_config.py +153 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
You are a senior engineer reviewing a pull request for synix-agent-mesh, an
|
|
2
|
+
orchestration layer that composes synix (build system for agent memory), synix
|
|
3
|
+
mesh (cross-device session sync), and MCP into a single CLI tool. You have
|
|
4
|
+
NEVER seen the codebase before. You are reviewing this PR using only the README
|
|
5
|
+
and the diff.
|
|
6
|
+
|
|
7
|
+
## Your context
|
|
8
|
+
|
|
9
|
+
You have been given two documents:
|
|
10
|
+
1. **README.md** — the full project documentation including architecture, CLI
|
|
11
|
+
reference, configuration, and multi-machine setup
|
|
12
|
+
2. **The PR diff**
|
|
13
|
+
|
|
14
|
+
This is an orchestration/glue project — it is intentionally thin (~500 lines)
|
|
15
|
+
and delegates heavy lifting to synix core, synix mesh, and synix MCP. The CLI
|
|
16
|
+
is called `sam`.
|
|
17
|
+
|
|
18
|
+
## What to evaluate
|
|
19
|
+
|
|
20
|
+
### Orchestration correctness
|
|
21
|
+
Does this change correctly compose the underlying synix components? Does it
|
|
22
|
+
respect the separation between synix-agent-mesh (orchestration) and synix
|
|
23
|
+
(core build system)? Flag any change that duplicates functionality that
|
|
24
|
+
belongs in synix core.
|
|
25
|
+
|
|
26
|
+
### Configuration coherence
|
|
27
|
+
The project uses a single `agent-mesh.toml` config file. Does this change
|
|
28
|
+
maintain config consistency? Are new settings documented? Do environment
|
|
29
|
+
variable overrides work correctly?
|
|
30
|
+
|
|
31
|
+
### Multi-machine concerns
|
|
32
|
+
The system runs across multiple machines (server, client, MCP router). Does
|
|
33
|
+
this change work correctly in all roles? Are there assumptions about being
|
|
34
|
+
the server that would break on client nodes? Are ports and hostnames
|
|
35
|
+
configurable?
|
|
36
|
+
|
|
37
|
+
### CLI ergonomics
|
|
38
|
+
Is the CLI interface intuitive? Are error messages helpful? Does the output
|
|
39
|
+
formatting make sense? Would a user running `sam serve` or `sam join` for the
|
|
40
|
+
first time understand what's happening?
|
|
41
|
+
|
|
42
|
+
### Async and process management
|
|
43
|
+
The server runs mesh server + MCP HTTP + viewer + local client watcher in a
|
|
44
|
+
single process using asyncio + threading. Does this change handle shutdown
|
|
45
|
+
correctly? Are there potential deadlocks or resource leaks?
|
|
46
|
+
|
|
47
|
+
### Legibility
|
|
48
|
+
Can you understand what the code does from the diff alone? Are names clear?
|
|
49
|
+
Would a new contributor be confused by anything?
|
|
50
|
+
|
|
51
|
+
### Test coverage
|
|
52
|
+
Does the diff include tests? Do the tests cover the happy path AND edge cases?
|
|
53
|
+
Flag any behavioral changes that appear untested.
|
|
54
|
+
|
|
55
|
+
## Output format
|
|
56
|
+
|
|
57
|
+
Write a structured review with these sections:
|
|
58
|
+
|
|
59
|
+
**Summary** — What this PR does in 2-3 sentences.
|
|
60
|
+
|
|
61
|
+
**Observations** — Numbered list of specific findings. Each should reference a
|
|
62
|
+
file or code pattern from the diff. Categorize each as: [concern], [question],
|
|
63
|
+
[nit], or [positive].
|
|
64
|
+
|
|
65
|
+
**Verdict** — One sentence: does this PR seem like a good incremental step for
|
|
66
|
+
the project?
|
|
67
|
+
|
|
68
|
+
Keep the total review under 600 words. Be direct. Skip generic praise.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
You are a skeptical systems architect doing a design review of a pull request.
|
|
2
|
+
Your job is to find problems. You are not here to be encouraging. You are here
|
|
3
|
+
to protect the project from bad decisions.
|
|
4
|
+
|
|
5
|
+
The project is synix-agent-mesh — an orchestration layer that wires together
|
|
6
|
+
synix (build system for agent memory), synix mesh (cross-device session sync),
|
|
7
|
+
and MCP into a single CLI. It is pre-1.0 and under active development. You
|
|
8
|
+
have NEVER seen the codebase. You are working from documentation and the diff
|
|
9
|
+
only.
|
|
10
|
+
|
|
11
|
+
## Your context
|
|
12
|
+
|
|
13
|
+
You have two documents:
|
|
14
|
+
1. **README.md** — full project documentation
|
|
15
|
+
2. **The PR diff**
|
|
16
|
+
|
|
17
|
+
## Your mandate
|
|
18
|
+
|
|
19
|
+
Hunt for problems. Specifically:
|
|
20
|
+
|
|
21
|
+
### One-way doors
|
|
22
|
+
Decisions that are hard or impossible to reverse once shipped. Examples:
|
|
23
|
+
- CLI commands or flags that users will depend on
|
|
24
|
+
- Configuration key names that get documented and relied upon
|
|
25
|
+
- Port numbers or protocol choices that become part of deployment guides
|
|
26
|
+
- Mesh protocol changes that affect multi-machine compatibility
|
|
27
|
+
|
|
28
|
+
For each one-way door: name it, explain why it is hard to reverse, and suggest
|
|
29
|
+
what would need to be true for it to be safe to merge.
|
|
30
|
+
|
|
31
|
+
### Orchestration failures
|
|
32
|
+
- Error handling gaps when synix core APIs change or fail
|
|
33
|
+
- Assumptions about synix internals that could break on version upgrades
|
|
34
|
+
- Missing graceful degradation (e.g., viewer unavailable, mesh unreachable)
|
|
35
|
+
- Process lifecycle issues (zombie processes, unhandled signals, port conflicts)
|
|
36
|
+
|
|
37
|
+
### Multi-machine risks
|
|
38
|
+
- Changes that work on the server but break on client nodes
|
|
39
|
+
- Hardcoded hostnames, paths, or ports that should be configurable
|
|
40
|
+
- Token or credential handling issues
|
|
41
|
+
- Network timeout or retry gaps
|
|
42
|
+
|
|
43
|
+
### What is NOT in the diff
|
|
44
|
+
- Missing tests for behavioral changes
|
|
45
|
+
- Missing error handling for plausible failure modes
|
|
46
|
+
- Missing config documentation when new settings are added
|
|
47
|
+
- Regression risks: does this change something that other code depends on?
|
|
48
|
+
|
|
49
|
+
### Hidden complexity
|
|
50
|
+
- Changes that look simple but have non-obvious downstream effects
|
|
51
|
+
- Implicit dependencies between the server, client, and MCP components
|
|
52
|
+
- Assumptions about execution order or environment state
|
|
53
|
+
- Magic numbers, hardcoded paths, or configuration that should be parameterized
|
|
54
|
+
|
|
55
|
+
## Output format
|
|
56
|
+
|
|
57
|
+
**Threat assessment** — One sentence: how risky is this PR?
|
|
58
|
+
|
|
59
|
+
**One-way doors** — Numbered list. If none found, say "None identified."
|
|
60
|
+
|
|
61
|
+
**Findings** — Numbered list of problems found. Each must:
|
|
62
|
+
1. Name the specific file and code pattern
|
|
63
|
+
2. Explain the failure mode or risk
|
|
64
|
+
3. Rate severity: [critical], [warning], or [minor]
|
|
65
|
+
|
|
66
|
+
**Missing** — What you expected to see but did not. Be specific.
|
|
67
|
+
|
|
68
|
+
**Verdict** — Ship it, ship with fixes, or block. One sentence with reasoning.
|
|
69
|
+
|
|
70
|
+
Be blunt. If the PR looks clean, say so in two sentences and move on. Do not
|
|
71
|
+
manufacture concerns to fill space. But do not go easy either.
|
|
72
|
+
|
|
73
|
+
Keep the total review under 700 words.
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""Blind PR review — posts an LLM review using only docs + diff as context."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from datetime import UTC, datetime
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
# Configuration
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
MAX_DIFF_LINES = 10_000
|
|
17
|
+
LLM_TIMEOUT = 120 # seconds
|
|
18
|
+
|
|
19
|
+
CLAUDE_MODEL = "claude-opus-4-6"
|
|
20
|
+
OPENAI_MODEL = "gpt-5.4"
|
|
21
|
+
|
|
22
|
+
PROMPTS_DIR = Path(__file__).resolve().parent.parent / "prompts"
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# Context gathering
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def fetch_diff(repo: str, pr_number: str, token: str) -> str:
|
|
30
|
+
"""Fetch PR diff via GitHub API, falling back to git diff for large PRs."""
|
|
31
|
+
url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}"
|
|
32
|
+
headers = {
|
|
33
|
+
"Authorization": f"Bearer {token}",
|
|
34
|
+
"Accept": "application/vnd.github.v3.diff",
|
|
35
|
+
}
|
|
36
|
+
resp = requests.get(url, headers=headers, timeout=30)
|
|
37
|
+
if resp.status_code == 406:
|
|
38
|
+
# Diff too large for GitHub API — fall back to local git diff
|
|
39
|
+
print("Diff too large for API (406), falling back to git diff...")
|
|
40
|
+
return _git_diff_fallback(repo, pr_number, token)
|
|
41
|
+
resp.raise_for_status()
|
|
42
|
+
return resp.text
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _git_diff_fallback(repo: str, pr_number: str, token: str) -> str:
|
|
46
|
+
"""Compute diff locally using git when the API can't generate it."""
|
|
47
|
+
import subprocess
|
|
48
|
+
|
|
49
|
+
# Fetch PR metadata to get base/head refs
|
|
50
|
+
url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}"
|
|
51
|
+
headers = {
|
|
52
|
+
"Authorization": f"Bearer {token}",
|
|
53
|
+
"Accept": "application/vnd.github.v3+json",
|
|
54
|
+
}
|
|
55
|
+
resp = requests.get(url, headers=headers, timeout=15)
|
|
56
|
+
resp.raise_for_status()
|
|
57
|
+
pr_data = resp.json()
|
|
58
|
+
base_sha = pr_data["base"]["sha"]
|
|
59
|
+
head_sha = pr_data["head"]["sha"]
|
|
60
|
+
|
|
61
|
+
result = subprocess.run(
|
|
62
|
+
["git", "diff", f"{base_sha}...{head_sha}"],
|
|
63
|
+
capture_output=True,
|
|
64
|
+
text=True,
|
|
65
|
+
timeout=30,
|
|
66
|
+
)
|
|
67
|
+
if result.returncode != 0:
|
|
68
|
+
raise RuntimeError(f"git diff failed: {result.stderr}")
|
|
69
|
+
return result.stdout
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def truncate_diff(diff: str) -> tuple[str, int, int]:
|
|
73
|
+
"""Truncate diff to MAX_DIFF_LINES. Returns (diff, shown_lines, total_lines)."""
|
|
74
|
+
lines = diff.splitlines()
|
|
75
|
+
total = len(lines)
|
|
76
|
+
if total <= MAX_DIFF_LINES:
|
|
77
|
+
return diff, total, total
|
|
78
|
+
truncated = "\n".join(lines[:MAX_DIFF_LINES])
|
|
79
|
+
truncated += f"\n\n... [diff truncated: showing first {MAX_DIFF_LINES:,} of {total:,} lines]"
|
|
80
|
+
return truncated, MAX_DIFF_LINES, total
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def load_prompt(provider: str) -> str:
|
|
84
|
+
"""Load the review prompt for the given provider."""
|
|
85
|
+
prompt_file = PROMPTS_DIR / f"{provider}_review.md"
|
|
86
|
+
return prompt_file.read_text()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def read_file(name: str) -> str:
|
|
90
|
+
"""Read a file from the workspace."""
|
|
91
|
+
workspace = Path(os.environ.get("GITHUB_WORKSPACE", "."))
|
|
92
|
+
path = workspace / name
|
|
93
|
+
if path.exists():
|
|
94
|
+
return path.read_text()
|
|
95
|
+
return f"({name} not found)"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
# LLM calls
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def build_user_message(readme: str, diff: str) -> str:
|
|
104
|
+
"""Compose the user message with context sections."""
|
|
105
|
+
return f"""## README.md
|
|
106
|
+
|
|
107
|
+
{readme}
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## PR Diff
|
|
112
|
+
|
|
113
|
+
```diff
|
|
114
|
+
{diff}
|
|
115
|
+
```"""
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def review_claude(system_prompt: str, user_message: str) -> tuple[str, str]:
|
|
119
|
+
"""Call Claude API with extended thinking. Returns (review_text, model_name)."""
|
|
120
|
+
import anthropic
|
|
121
|
+
|
|
122
|
+
client = anthropic.Anthropic()
|
|
123
|
+
response = client.messages.create(
|
|
124
|
+
model=CLAUDE_MODEL,
|
|
125
|
+
max_tokens=16000,
|
|
126
|
+
temperature=1, # required for extended thinking
|
|
127
|
+
thinking={
|
|
128
|
+
"type": "enabled",
|
|
129
|
+
"budget_tokens": 10000,
|
|
130
|
+
},
|
|
131
|
+
system=system_prompt,
|
|
132
|
+
messages=[{"role": "user", "content": user_message}],
|
|
133
|
+
)
|
|
134
|
+
# Extract the text block (skip thinking blocks)
|
|
135
|
+
for block in response.content:
|
|
136
|
+
if block.type == "text":
|
|
137
|
+
return block.text, CLAUDE_MODEL
|
|
138
|
+
return "(no text in response)", CLAUDE_MODEL
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def review_openai(system_prompt: str, user_message: str) -> tuple[str, str]:
|
|
142
|
+
"""Call OpenAI API. Returns (review_text, model_name)."""
|
|
143
|
+
import openai
|
|
144
|
+
|
|
145
|
+
client = openai.OpenAI()
|
|
146
|
+
response = client.chat.completions.create(
|
|
147
|
+
model=OPENAI_MODEL,
|
|
148
|
+
messages=[
|
|
149
|
+
{"role": "developer", "content": system_prompt},
|
|
150
|
+
{"role": "user", "content": user_message},
|
|
151
|
+
],
|
|
152
|
+
)
|
|
153
|
+
return response.choices[0].message.content, OPENAI_MODEL
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# ---------------------------------------------------------------------------
|
|
157
|
+
# GitHub interaction
|
|
158
|
+
# ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def post_comment(repo: str, pr_number: str, token: str, body: str) -> None:
|
|
162
|
+
"""Post a comment on the PR via GitHub API."""
|
|
163
|
+
url = f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments"
|
|
164
|
+
headers = {
|
|
165
|
+
"Authorization": f"Bearer {token}",
|
|
166
|
+
"Accept": "application/vnd.github.v3+json",
|
|
167
|
+
}
|
|
168
|
+
resp = requests.post(url, headers=headers, json={"body": body}, timeout=15)
|
|
169
|
+
resp.raise_for_status()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def format_comment(
|
|
173
|
+
review: str,
|
|
174
|
+
provider: str,
|
|
175
|
+
model: str,
|
|
176
|
+
diff_shown: int,
|
|
177
|
+
diff_total: int,
|
|
178
|
+
prompt_file: str,
|
|
179
|
+
) -> str:
|
|
180
|
+
"""Format the review as a PR comment."""
|
|
181
|
+
timestamp = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
182
|
+
|
|
183
|
+
if provider == "claude":
|
|
184
|
+
header = "**Architectural Review** — Claude Opus | Blind review (docs + diff only)"
|
|
185
|
+
else:
|
|
186
|
+
header = "**Red Team Review** — OpenAI GPT-5.4 | Adversarial review (docs + diff only)"
|
|
187
|
+
|
|
188
|
+
diff_note = f"{diff_shown:,} lines"
|
|
189
|
+
if diff_shown < diff_total:
|
|
190
|
+
diff_note += f" (of {diff_total:,} total — truncated)"
|
|
191
|
+
|
|
192
|
+
return f"""> [!NOTE]
|
|
193
|
+
> {header}
|
|
194
|
+
|
|
195
|
+
{review}
|
|
196
|
+
|
|
197
|
+
<details>
|
|
198
|
+
<summary>Review parameters</summary>
|
|
199
|
+
|
|
200
|
+
- **Model**: `{model}`
|
|
201
|
+
- **Context**: README.md, PR diff
|
|
202
|
+
- **Diff size**: {diff_note}
|
|
203
|
+
- **Prompt**: `{prompt_file}`
|
|
204
|
+
- **Timestamp**: {timestamp}
|
|
205
|
+
|
|
206
|
+
</details>
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# ---------------------------------------------------------------------------
|
|
211
|
+
# Main
|
|
212
|
+
# ---------------------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def main() -> None:
|
|
216
|
+
provider = os.environ["PROVIDER"] # "claude" or "openai"
|
|
217
|
+
token = os.environ["GITHUB_TOKEN"]
|
|
218
|
+
pr_number = os.environ["PR_NUMBER"]
|
|
219
|
+
repo = os.environ["REPO"]
|
|
220
|
+
|
|
221
|
+
# 1. Gather context
|
|
222
|
+
print(f"Fetching diff for {repo}#{pr_number}...")
|
|
223
|
+
raw_diff = fetch_diff(repo, pr_number, token)
|
|
224
|
+
diff, diff_shown, diff_total = truncate_diff(raw_diff)
|
|
225
|
+
print(f"Diff: {diff_total:,} lines ({diff_shown:,} shown)")
|
|
226
|
+
|
|
227
|
+
readme = read_file("README.md")
|
|
228
|
+
|
|
229
|
+
# 2. Build prompt and call LLM
|
|
230
|
+
system_prompt = load_prompt(provider)
|
|
231
|
+
user_message = build_user_message(readme, diff)
|
|
232
|
+
prompt_file = f".github/prompts/{provider}_review.md"
|
|
233
|
+
|
|
234
|
+
print(f"Calling {provider} API ({CLAUDE_MODEL if provider == 'claude' else OPENAI_MODEL})...")
|
|
235
|
+
try:
|
|
236
|
+
if provider == "claude":
|
|
237
|
+
review, model = review_claude(system_prompt, user_message)
|
|
238
|
+
elif provider == "openai":
|
|
239
|
+
review, model = review_openai(system_prompt, user_message)
|
|
240
|
+
else:
|
|
241
|
+
print(f"Unknown provider: {provider}", file=sys.stderr)
|
|
242
|
+
sys.exit(1)
|
|
243
|
+
except Exception as exc:
|
|
244
|
+
error_type = type(exc).__name__
|
|
245
|
+
fail_body = (
|
|
246
|
+
f"> [!WARNING]\n"
|
|
247
|
+
f"> **AI Review failed** — {provider} | `{error_type}`\n\n"
|
|
248
|
+
f"The {provider} blind review could not be completed. "
|
|
249
|
+
f"This does not affect the PR.\n"
|
|
250
|
+
)
|
|
251
|
+
print(f"LLM call failed ({error_type}): {exc}", file=sys.stderr)
|
|
252
|
+
post_comment(repo, pr_number, token, fail_body)
|
|
253
|
+
sys.exit(0) # don't red-X the PR
|
|
254
|
+
|
|
255
|
+
# 3. Post review comment
|
|
256
|
+
print(f"Posting review ({len(review)} chars)...")
|
|
257
|
+
body = format_comment(review, provider, model, diff_shown, diff_total, prompt_file)
|
|
258
|
+
post_comment(repo, pr_number, token, body)
|
|
259
|
+
print("Done.")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
if __name__ == "__main__":
|
|
263
|
+
main()
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
name: Blind PR Review
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request_target:
|
|
5
|
+
types: [opened, synchronize]
|
|
6
|
+
|
|
7
|
+
env:
|
|
8
|
+
APPROVED_CONTRIBUTORS: '["marklubin"]'
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
gate:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
outputs:
|
|
14
|
+
approved: ${{ steps.check.outputs.approved }}
|
|
15
|
+
steps:
|
|
16
|
+
- id: check
|
|
17
|
+
run: |
|
|
18
|
+
ACTOR="${{ github.actor }}"
|
|
19
|
+
if echo '${{ env.APPROVED_CONTRIBUTORS }}' | jq -e ". | index(\"$ACTOR\")" > /dev/null 2>&1; then
|
|
20
|
+
echo "approved=true" >> "$GITHUB_OUTPUT"
|
|
21
|
+
echo "::notice::Blind review approved for $ACTOR"
|
|
22
|
+
else
|
|
23
|
+
echo "approved=false" >> "$GITHUB_OUTPUT"
|
|
24
|
+
echo "::notice::Skipping blind review — $ACTOR is not in approved list"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
review-claude:
|
|
28
|
+
needs: gate
|
|
29
|
+
if: needs.gate.outputs.approved == 'true'
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
permissions:
|
|
32
|
+
pull-requests: write
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
with:
|
|
36
|
+
ref: ${{ github.event.repository.default_branch }}
|
|
37
|
+
sparse-checkout: |
|
|
38
|
+
.github/scripts/blind_review.py
|
|
39
|
+
.github/prompts/claude_review.md
|
|
40
|
+
.github/prompts/openai_review.md
|
|
41
|
+
README.md
|
|
42
|
+
sparse-checkout-cone-mode: false
|
|
43
|
+
|
|
44
|
+
- uses: actions/setup-python@v5
|
|
45
|
+
with:
|
|
46
|
+
python-version: "3.12"
|
|
47
|
+
|
|
48
|
+
- run: pip install anthropic requests
|
|
49
|
+
|
|
50
|
+
- run: python .github/scripts/blind_review.py
|
|
51
|
+
env:
|
|
52
|
+
PROVIDER: claude
|
|
53
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
54
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
55
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
56
|
+
REPO: ${{ github.repository }}
|
|
57
|
+
|
|
58
|
+
review-openai:
|
|
59
|
+
needs: gate
|
|
60
|
+
if: needs.gate.outputs.approved == 'true'
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
permissions:
|
|
63
|
+
pull-requests: write
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/checkout@v4
|
|
66
|
+
with:
|
|
67
|
+
ref: ${{ github.event.repository.default_branch }}
|
|
68
|
+
sparse-checkout: |
|
|
69
|
+
.github/scripts/blind_review.py
|
|
70
|
+
.github/prompts/claude_review.md
|
|
71
|
+
.github/prompts/openai_review.md
|
|
72
|
+
README.md
|
|
73
|
+
sparse-checkout-cone-mode: false
|
|
74
|
+
|
|
75
|
+
- uses: actions/setup-python@v5
|
|
76
|
+
with:
|
|
77
|
+
python-version: "3.12"
|
|
78
|
+
|
|
79
|
+
- run: pip install openai requests
|
|
80
|
+
|
|
81
|
+
- run: python .github/scripts/blind_review.py
|
|
82
|
+
env:
|
|
83
|
+
PROVIDER: openai
|
|
84
|
+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
85
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
86
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
87
|
+
REPO: ${{ github.repository }}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["*"]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: astral-sh/setup-uv@v4
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ matrix.python-version }}
|
|
20
|
+
- run: uv sync
|
|
21
|
+
- run: uv run ruff check .
|
|
22
|
+
- run: uv run pytest tests/ -v
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
id-token: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: astral-sh/setup-uv@v4
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python-version }}
|
|
21
|
+
- run: uv sync
|
|
22
|
+
- run: uv run ruff check .
|
|
23
|
+
- run: uv run pytest tests/ -v
|
|
24
|
+
|
|
25
|
+
publish:
|
|
26
|
+
needs: [test]
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
environment: pypi
|
|
29
|
+
permissions:
|
|
30
|
+
id-token: write
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
- uses: astral-sh/setup-uv@v4
|
|
34
|
+
with:
|
|
35
|
+
python-version: "3.12"
|
|
36
|
+
- run: uv build
|
|
37
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
38
|
+
|
|
39
|
+
smoke-test:
|
|
40
|
+
needs: publish
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
steps:
|
|
43
|
+
- uses: astral-sh/setup-uv@v4
|
|
44
|
+
with:
|
|
45
|
+
python-version: "3.12"
|
|
46
|
+
- name: Wait for PyPI propagation
|
|
47
|
+
run: sleep 30
|
|
48
|
+
- name: Install from PyPI
|
|
49
|
+
run: uv tool install synix-agent-mesh
|
|
50
|
+
- name: Verify CLI
|
|
51
|
+
run: |
|
|
52
|
+
sam --help
|
|
53
|
+
sam --version
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mark Lubin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|