sid-harness 0.3.0__py3-none-any.whl

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 (45) hide show
  1. sid_harness/__init__.py +13 -0
  2. sid_harness/__main__.py +4 -0
  3. sid_harness/_native.py +1639 -0
  4. sid_harness/brave_proof.py +246 -0
  5. sid_harness/bridge.py +851 -0
  6. sid_harness/cli.py +937 -0
  7. sid_harness/client.py +161 -0
  8. sid_harness/companion.py +151 -0
  9. sid_harness/config.py +68 -0
  10. sid_harness/context/__init__.py +40 -0
  11. sid_harness/context/index.py +267 -0
  12. sid_harness/context/retrieval.py +74 -0
  13. sid_harness/editor.py +231 -0
  14. sid_harness/gateway.py +106 -0
  15. sid_harness/git_write.py +598 -0
  16. sid_harness/local_workspace.py +421 -0
  17. sid_harness/loop.py +394 -0
  18. sid_harness/lsp.py +598 -0
  19. sid_harness/policy/__init__.py +44 -0
  20. sid_harness/policy/repo_policy.py +626 -0
  21. sid_harness/receipt.py +142 -0
  22. sid_harness/runner.py +468 -0
  23. sid_harness/selftest.py +149 -0
  24. sid_harness/session/__init__.py +33 -0
  25. sid_harness/session/bounds.py +140 -0
  26. sid_harness/session/diff_gate.py +93 -0
  27. sid_harness/session/session.py +427 -0
  28. sid_harness/session/store.py +88 -0
  29. sid_harness/tools/__init__.py +52 -0
  30. sid_harness/tools/mcp_client.py +285 -0
  31. sid_harness/tools/plan.py +138 -0
  32. sid_harness/tools/registry.py +159 -0
  33. sid_harness/tools/skills.py +185 -0
  34. sid_harness/tools/subagents.py +112 -0
  35. sid_harness/tui.py +96 -0
  36. sid_harness/workspace.py +107 -0
  37. sid_harness-0.3.0.data/data/share/sid-harness/editors/README.md +46 -0
  38. sid_harness-0.3.0.data/data/share/sid-harness/editors/vscode/README.md +32 -0
  39. sid_harness-0.3.0.data/data/share/sid-harness/editors/vscode/extension.js +395 -0
  40. sid_harness-0.3.0.data/data/share/sid-harness/editors/vscode/package.json +120 -0
  41. sid_harness-0.3.0.dist-info/METADATA +229 -0
  42. sid_harness-0.3.0.dist-info/RECORD +45 -0
  43. sid_harness-0.3.0.dist-info/WHEEL +5 -0
  44. sid_harness-0.3.0.dist-info/entry_points.txt +2 -0
  45. sid_harness-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,246 @@
1
+ """Live Brave proof: pair -> grant write -> run -> review diff -> apply to disk.
2
+
3
+ This drives the real protocol from a page executed by the installed Brave
4
+ browser (headless), against a real loopback bridge. The model call is stubbed
5
+ only because a proof must not spend a customer's SID key; everything else —
6
+ origin allowlist, pairing code exchange, explicit write grant, proposal digest,
7
+ denylist validation, atomic write, receipt — is the production path.
8
+
9
+ Run ``python -m sid_harness.brave_proof`` or ``install/prove_brave_apply.py``.
10
+ Exit 0 on a real applied write. Exit 2 when Brave is not installed (honest
11
+ skip), 1 on any failure.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import os
18
+ import shutil
19
+ import subprocess
20
+ import sys
21
+ import tempfile
22
+ import threading
23
+ import time
24
+ import urllib.parse
25
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
26
+ from pathlib import Path
27
+
28
+ from . import bridge as bridge_mod
29
+
30
+ BRAVE_CANDIDATES = (
31
+ r"C:\Program Files\BraveSoftware\Brave-Browser\Application\brave.exe",
32
+ r"C:\Program Files (x86)\BraveSoftware\Brave-Browser\Application\brave.exe",
33
+ os.path.expandvars(r"%LOCALAPPDATA%\BraveSoftware\Brave-Browser\Application\brave.exe"),
34
+ )
35
+
36
+
37
+ def find_brave() -> str | None:
38
+ for candidate in BRAVE_CANDIDATES:
39
+ if candidate and Path(candidate).is_file():
40
+ return candidate
41
+ found = shutil.which("brave-browser") or shutil.which("brave") or shutil.which("brave.exe")
42
+ return found
43
+
44
+
45
+ PAGE = """<!doctype html>
46
+ <meta charset="utf-8">
47
+ <title>SID bridge Brave proof</title>
48
+ <body>
49
+ <pre id="log">starting</pre>
50
+ <script>
51
+ const q = new URLSearchParams(location.search);
52
+ const bridge = q.get("bridge");
53
+ const code = q.get("code");
54
+ const log = (s) => { document.getElementById("log").textContent += "\\n" + s; };
55
+ async function jpost(path, body, headers) {
56
+ const resp = await fetch(bridge + path, {
57
+ method: "POST",
58
+ headers: Object.assign({ "Content-Type": "application/json" }, headers || {}),
59
+ body: JSON.stringify(body || {}),
60
+ });
61
+ let data = {};
62
+ try { data = await resp.json(); } catch (_) {}
63
+ if (!resp.ok) throw new Error(path + " HTTP " + resp.status + " " + (data.error || ""));
64
+ return data;
65
+ }
66
+ (async () => {
67
+ try {
68
+ log("pair");
69
+ const pair = await jpost("/pair", { code: code });
70
+ const auth = { "X-SID-Bridge-Token": pair.token };
71
+ log("grant");
72
+ await jpost("/grant-write", { granted: true, confirm: "enable-write" }, auth);
73
+ log("run");
74
+ const run = await jpost("/run", { task: "update index.html" }, Object.assign({
75
+ "X-SID-Api-Key": "proof-key",
76
+ }, auth));
77
+ if (!run.proposal) throw new Error("no proposal returned");
78
+ log("review " + run.proposal.digest);
79
+ const applied = await jpost("/apply", {
80
+ proposal_id: run.proposal.id,
81
+ digest: run.proposal.digest,
82
+ }, auth);
83
+ log("applied " + applied.written.join(","));
84
+ await fetch("/done?written=" + encodeURIComponent(applied.written.join(",")) +
85
+ "&digest=" + encodeURIComponent(run.proposal.digest));
86
+ } catch (e) {
87
+ log("error " + e.message);
88
+ await fetch("/fail?msg=" + encodeURIComponent(e.message || String(e)));
89
+ }
90
+ })();
91
+ </script>
92
+ """
93
+
94
+
95
+ class _PageHandler(BaseHTTPRequestHandler):
96
+ def log_message(self, fmt, *args):
97
+ pass
98
+
99
+ def do_GET(self): # noqa: N802
100
+ parsed = urllib.parse.urlparse(self.path)
101
+ if parsed.path == "/":
102
+ body = PAGE.encode("utf-8")
103
+ self.send_response(200)
104
+ self.send_header("Content-Type", "text/html; charset=utf-8")
105
+ self.send_header("Content-Length", str(len(body)))
106
+ self.end_headers()
107
+ self.wfile.write(body)
108
+ return
109
+ if parsed.path in ("/done", "/fail"):
110
+ params = urllib.parse.parse_qs(parsed.query)
111
+ payload = {key: values[0] for key, values in params.items()}
112
+ self.server.proof_result = {"ok": parsed.path == "/done", **payload} # type: ignore[attr-defined]
113
+ self.server.proof_event.set() # type: ignore[attr-defined]
114
+ self.send_response(204)
115
+ self.end_headers()
116
+ return
117
+ self.send_response(404)
118
+ self.end_headers()
119
+
120
+
121
+ def _fake_run_task(*, directory, task, api_key, base_url=None, model="auto", test_argv=None):
122
+ return {
123
+ "final_response": "Updated index.html after reading the real tree.",
124
+ "model": "proof-stub",
125
+ "steps": 2,
126
+ "tool_calls": 1,
127
+ "changed": ["index.html"],
128
+ "readback": {"index.html": "<h1>changed by SID through Brave</h1>\n"},
129
+ "diff": "--- a/index.html\n+++ b/index.html\n@@\n-<h1>hello</h1>\n+<h1>changed by SID through Brave</h1>\n",
130
+ "test": {"executed": False, "passed": None},
131
+ }
132
+
133
+
134
+ def run_proof(*, timeout: float = 45.0) -> dict:
135
+ brave = find_brave()
136
+ if not brave:
137
+ return {"ok": False, "skipped": True, "error": "Brave is not installed"}
138
+
139
+ tmp = Path(tempfile.mkdtemp(prefix="sid-brave-proof-"))
140
+ workspace = tmp / "workspace"
141
+ workspace.mkdir()
142
+ (workspace / "index.html").write_text("<h1>hello</h1>\n", encoding="utf-8")
143
+ (workspace / "app.js").write_text("console.log('hello')\n", encoding="utf-8")
144
+
145
+ page_server = ThreadingHTTPServer(("127.0.0.1", 0), _PageHandler)
146
+ page_server.daemon_threads = True
147
+ page_server.proof_event = threading.Event() # type: ignore[attr-defined]
148
+ page_server.proof_result = None # type: ignore[attr-defined]
149
+ page_thread = threading.Thread(target=page_server.serve_forever, daemon=True)
150
+ page_thread.start()
151
+ page_origin = f"http://127.0.0.1:{page_server.server_address[1]}"
152
+
153
+ original_run_task = bridge_mod.run_task
154
+ bridge_mod.run_task = _fake_run_task # type: ignore[assignment]
155
+ httpd = bridge_mod.serve(
156
+ workspace,
157
+ port=0,
158
+ token="proof-token",
159
+ write_capable=True,
160
+ pair_code="proof-pair-code",
161
+ pair_ttl=300,
162
+ state_dir=tmp / "state",
163
+ allowed_origins=(page_origin,),
164
+ )
165
+ bridge_thread = threading.Thread(target=httpd.serve_forever, daemon=True)
166
+ bridge_thread.start()
167
+ bridge_url = f"http://127.0.0.1:{httpd.server_address[1]}"
168
+
169
+ profile = tmp / "brave-profile"
170
+ url = (
171
+ f"{page_origin}/?"
172
+ + urllib.parse.urlencode({"bridge": bridge_url, "code": "proof-pair-code"})
173
+ )
174
+ args = [
175
+ brave,
176
+ "--headless=new",
177
+ "--disable-gpu",
178
+ "--no-first-run",
179
+ "--no-default-browser-check",
180
+ "--disable-background-networking",
181
+ "--disable-sync",
182
+ "--no-proxy-server",
183
+ f"--user-data-dir={profile}",
184
+ url,
185
+ ]
186
+ proc = None
187
+ try:
188
+ proc = subprocess.Popen(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
189
+ page_server.proof_event.wait(timeout) # type: ignore[attr-defined]
190
+ result = dict(page_server.proof_result or {}) # type: ignore[attr-defined]
191
+ finally:
192
+ if proc is not None:
193
+ try:
194
+ proc.terminate()
195
+ proc.wait(timeout=10)
196
+ except Exception:
197
+ try:
198
+ proc.kill()
199
+ except Exception:
200
+ pass
201
+ httpd.shutdown()
202
+ httpd.server_close()
203
+ page_server.shutdown()
204
+ page_server.server_close()
205
+ bridge_mod.run_task = original_run_task # type: ignore[assignment]
206
+
207
+ if not result:
208
+ return {"ok": False, "error": f"Brave did not finish the flow within {timeout:g}s"}
209
+ disk = (workspace / "index.html").read_text(encoding="utf-8")
210
+ receipts = sorted((tmp / "state" / "receipts").glob("apply_*.json"))
211
+ result.update({
212
+ "brave": brave,
213
+ "bridge": bridge_url,
214
+ "origin": page_origin,
215
+ "disk": disk,
216
+ "receipts": [str(path) for path in receipts],
217
+ "receipt": json.loads(receipts[-1].read_text(encoding="utf-8")) if receipts else None,
218
+ })
219
+ result["ok"] = bool(
220
+ result.get("ok")
221
+ and "changed by SID through Brave" in disk
222
+ and receipts
223
+ )
224
+ return result
225
+
226
+
227
+ def main(argv: list[str] | None = None) -> int:
228
+ import argparse
229
+
230
+ ap = argparse.ArgumentParser(description="Prove the Brave end-to-end apply path live.")
231
+ ap.add_argument("--timeout", type=float, default=45.0)
232
+ args = ap.parse_args(argv)
233
+ result = run_proof(timeout=args.timeout)
234
+ if result.get("skipped"):
235
+ print(f"brave_proof: SKIP — {result.get('error')}")
236
+ return 2
237
+ print(json.dumps(result, indent=2, sort_keys=True))
238
+ if result.get("ok"):
239
+ print("brave_proof: PASS — Brave applied the reviewed diff to disk")
240
+ return 0
241
+ print(f"brave_proof: FAIL — {result.get('error', 'unknown failure')}")
242
+ return 1
243
+
244
+
245
+ if __name__ == "__main__": # pragma: no cover
246
+ raise SystemExit(main())