wydcode 0.4.3

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 (41) hide show
  1. package/LICENSE +100 -0
  2. package/NOTICE +5 -0
  3. package/README.md +434 -0
  4. package/SECURITY.md +64 -0
  5. package/SOCRA_SOURCE.md +36 -0
  6. package/TRADEMARKS.md +12 -0
  7. package/bin/wydcode.js +74 -0
  8. package/examples/behavior-preference-card.json +12 -0
  9. package/examples/exact-recurrence-card.json +11 -0
  10. package/examples/heldout-gene-evidence.json +6 -0
  11. package/examples/humaneval-ornith.md +29 -0
  12. package/examples/large_context_json_diagnostic.py +187 -0
  13. package/fixtures/failing-node-repo/package.json +9 -0
  14. package/fixtures/failing-node-repo/src/math.js +3 -0
  15. package/fixtures/failing-node-repo/test/math.test.js +8 -0
  16. package/package.json +55 -0
  17. package/pyproject.toml +24 -0
  18. package/socra_harness/__init__.py +3 -0
  19. package/socra_harness/attempts.py +76 -0
  20. package/socra_harness/central_traces.py +627 -0
  21. package/socra_harness/cli.py +265 -0
  22. package/socra_harness/eval/__init__.py +1 -0
  23. package/socra_harness/eval/humaneval.py +388 -0
  24. package/socra_harness/events.py +39 -0
  25. package/socra_harness/init.py +206 -0
  26. package/socra_harness/job/__init__.py +2 -0
  27. package/socra_harness/job/events.py +132 -0
  28. package/socra_harness/job/lock.py +90 -0
  29. package/socra_harness/job/manager.py +3785 -0
  30. package/socra_harness/job/planner.py +253 -0
  31. package/socra_harness/job/state.py +106 -0
  32. package/socra_harness/mumpix_trace_bridge.cjs +32 -0
  33. package/socra_harness/product.py +533 -0
  34. package/socra_harness/recurrence.py +263 -0
  35. package/socra_harness/scan/__init__.py +1 -0
  36. package/socra_harness/scan/repo.py +85 -0
  37. package/socra_harness/serve/__init__.py +1 -0
  38. package/socra_harness/serve/local_proxy.py +293 -0
  39. package/socra_harness/start.py +331 -0
  40. package/socra_harness/tui/__init__.py +1 -0
  41. package/socra_harness/tui/app.py +1114 -0
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import time
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+
9
+ def now_ms() -> int:
10
+ return int(time.time() * 1000)
11
+
12
+
13
+ class EventLog:
14
+ def __init__(self, path: Path | None):
15
+ self.path = path
16
+ if self.path:
17
+ self.path.parent.mkdir(parents=True, exist_ok=True)
18
+
19
+ def emit(self, event: str, **payload: Any) -> None:
20
+ if not self.path:
21
+ return
22
+ row = {"ts_ms": now_ms(), "event": event, **payload}
23
+ with self.path.open("a", encoding="utf-8") as handle:
24
+ handle.write(json.dumps(row, sort_keys=True) + "\n")
25
+
26
+
27
+ def read_events(path: Path, limit: int = 200) -> list[dict[str, Any]]:
28
+ if not path.exists():
29
+ return []
30
+ if limit <= 0:
31
+ return []
32
+ lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
33
+ out = []
34
+ for line in lines[-limit:]:
35
+ try:
36
+ out.append(json.loads(line))
37
+ except json.JSONDecodeError:
38
+ continue
39
+ return out
@@ -0,0 +1,206 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ import subprocess
7
+ import urllib.error
8
+ import urllib.request
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+
13
+ DEFAULT_ENDPOINT = "http://127.0.0.1:11439/v1"
14
+ DEFAULT_CONTEXT_BUDGET = 8192
15
+ DEFAULT_MAX_OUTPUT = 8192
16
+
17
+
18
+ def run_git(project: Path, args: list[str]) -> subprocess.CompletedProcess[str]:
19
+ return subprocess.run(
20
+ ["git", *args],
21
+ cwd=project,
22
+ text=True,
23
+ stdout=subprocess.PIPE,
24
+ stderr=subprocess.PIPE,
25
+ check=False,
26
+ )
27
+
28
+
29
+ def require_git_repo(project: Path) -> Path:
30
+ result = run_git(project, ["rev-parse", "--show-toplevel"])
31
+ if result.returncode != 0:
32
+ raise SystemExit("wydcode init requires a git repository. Run git init first.")
33
+ return Path(result.stdout.strip()).resolve()
34
+
35
+
36
+ def git_dirty(project: Path) -> bool:
37
+ result = run_git(project, ["status", "--porcelain"])
38
+ if result.returncode != 0:
39
+ return True
40
+ return bool(result.stdout.strip())
41
+
42
+
43
+ def detect_package_manager(project: Path) -> str | None:
44
+ if (project / "pnpm-lock.yaml").exists():
45
+ return "pnpm"
46
+ if (project / "yarn.lock").exists():
47
+ return "yarn"
48
+ if (project / "bun.lockb").exists() or (project / "bun.lock").exists():
49
+ return "bun"
50
+ if (project / "package-lock.json").exists():
51
+ return "npm"
52
+ if (project / "package.json").exists():
53
+ return "npm"
54
+ return None
55
+
56
+
57
+ def load_package_json(project: Path) -> dict[str, Any]:
58
+ path = project / "package.json"
59
+ if not path.exists():
60
+ return {}
61
+ try:
62
+ value = json.loads(path.read_text(encoding="utf-8"))
63
+ except json.JSONDecodeError as exc:
64
+ raise SystemExit(f"package.json is not valid JSON: {exc}") from exc
65
+ if not isinstance(value, dict):
66
+ raise SystemExit("package.json must contain a JSON object.")
67
+ return value
68
+
69
+
70
+ def package_command(package_manager: str, script: str) -> list[str]:
71
+ if package_manager == "npm":
72
+ return ["npm", "run", script]
73
+ if package_manager == "pnpm":
74
+ return ["pnpm", script]
75
+ if package_manager == "yarn":
76
+ return ["yarn", script]
77
+ if package_manager == "bun":
78
+ return ["bun", "run", script]
79
+ return [package_manager, "run", script]
80
+
81
+
82
+ def detect_test_command(project: Path, package_manager: str | None) -> list[str] | None:
83
+ package = load_package_json(project)
84
+ scripts = package.get("scripts") if isinstance(package.get("scripts"), dict) else {}
85
+ if package_manager and isinstance(scripts, dict):
86
+ # Prefer explicitly finite CI/run scripts over watch-oriented defaults.
87
+ for script in ["test:run", "test:ci", "ci:test", "test", "check", "build"]:
88
+ value = scripts.get(script)
89
+ if isinstance(value, str) and value.strip():
90
+ return package_command(package_manager, script)
91
+ return None
92
+
93
+
94
+ def detect_project_type(project: Path) -> str:
95
+ if (project / "package.json").exists():
96
+ package = load_package_json(project)
97
+ deps = {}
98
+ for key in ["dependencies", "devDependencies"]:
99
+ if isinstance(package.get(key), dict):
100
+ deps.update(package[key])
101
+ if "next" in deps:
102
+ return "node-nextjs"
103
+ if "react" in deps or "vite" in deps:
104
+ return "node-frontend"
105
+ return "node"
106
+ return "unknown"
107
+
108
+
109
+ def probe_endpoint(endpoint: str, timeout: float = 1.0) -> dict[str, Any]:
110
+ base = endpoint.rstrip("/")
111
+ candidates = [base.removesuffix("/v1") + "/health", base + "/models"]
112
+ errors: list[str] = []
113
+ for url in candidates:
114
+ try:
115
+ with urllib.request.urlopen(url, timeout=timeout) as response:
116
+ body = response.read(4096).decode("utf-8", errors="replace")
117
+ parsed: Any
118
+ try:
119
+ parsed = json.loads(body) if body else {}
120
+ except json.JSONDecodeError:
121
+ parsed = {"raw": body[:256]}
122
+ return {"ok": True, "url": url, "status": "reachable", "response": parsed}
123
+ except (OSError, urllib.error.URLError, TimeoutError) as exc:
124
+ errors.append(f"{url}: {exc}")
125
+ return {"ok": False, "status": "unreachable", "errors": errors}
126
+
127
+
128
+ def build_config(args: argparse.Namespace, project: Path, repo_root: Path) -> dict[str, Any]:
129
+ package_manager = detect_package_manager(repo_root)
130
+ test_command = detect_test_command(repo_root, package_manager)
131
+ endpoint = args.endpoint or os.environ.get("SOCRA_ENDPOINT") or DEFAULT_ENDPOINT
132
+ model = args.model or os.environ.get("SOCRA_MODEL")
133
+ endpoint_probe = probe_endpoint(endpoint) if not args.skip_endpoint_probe else {"ok": None, "status": "skipped"}
134
+ return {
135
+ "schema_version": 1,
136
+ "tool": "socra-harness",
137
+ "project_root": str(repo_root),
138
+ "initialized_from": str(project),
139
+ "git": {
140
+ "required": True,
141
+ "root": str(repo_root),
142
+ "dirty_at_init": git_dirty(repo_root),
143
+ },
144
+ "project": {
145
+ "type": detect_project_type(repo_root),
146
+ "package_manager": package_manager,
147
+ "test_command": test_command,
148
+ },
149
+ "model": {
150
+ "endpoint": endpoint,
151
+ "model": model,
152
+ "local_first": True,
153
+ "hosted_fallback": False,
154
+ "endpoint_probe": endpoint_probe,
155
+ },
156
+ "budgets": {
157
+ "context_budget_tokens": args.context_budget,
158
+ "max_output_tokens": args.max_output,
159
+ },
160
+ "output_contract": {
161
+ "record_finish_reason": True,
162
+ "record_content_chars": True,
163
+ "record_reasoning_chars": True,
164
+ "classify_length_without_content_separately": True,
165
+ "prefer_module_sized_tasks": True,
166
+ },
167
+ "promotion": {
168
+ "source_writes_require_promote": True,
169
+ "review_and_approval_are_separate_steps": True,
170
+ "approval_is_hash_bound": True,
171
+ },
172
+ "traces": {
173
+ "cross_project": True,
174
+ "home": "~/.wydcode/traces",
175
+ "source_of_truth": "hash_chained_jsonl",
176
+ "mumpix_index": True,
177
+ },
178
+ }
179
+
180
+
181
+ def init_main(args: argparse.Namespace) -> int:
182
+ project = args.project.expanduser().resolve()
183
+ if not project.exists():
184
+ raise SystemExit(f"Project path does not exist: {project}")
185
+ if not project.is_dir():
186
+ raise SystemExit(f"Project path is not a directory: {project}")
187
+
188
+ repo_root = require_git_repo(project)
189
+ config = build_config(args, project, repo_root)
190
+ socra_dir = repo_root / ".socra"
191
+ socra_dir.mkdir(parents=True, exist_ok=True)
192
+ config_path = socra_dir / "config.json"
193
+ if config_path.exists() and not args.force:
194
+ raise SystemExit(f"{config_path} already exists. Pass --force to overwrite.")
195
+ config_path.write_text(json.dumps(config, indent=2, sort_keys=True) + "\n", encoding="utf-8")
196
+
197
+ print(f"Initialized Socra config: {config_path}")
198
+ print(f"Project: {repo_root}")
199
+ if config["git"]["dirty_at_init"]:
200
+ print("Warning: git worktree is dirty at init.")
201
+ package_manager = config["project"]["package_manager"] or "not detected"
202
+ print(f"Package manager: {package_manager}")
203
+ print(f"Test command: {' '.join(config['project']['test_command']) if config['project']['test_command'] else 'not detected'}")
204
+ probe = config["model"]["endpoint_probe"]
205
+ print(f"Endpoint: {config['model']['endpoint']} ({probe.get('status')})")
206
+ return 0
@@ -0,0 +1,2 @@
1
+ """Durable long-running Socra job primitives."""
2
+
@@ -0,0 +1,132 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import datetime as dt
5
+ import hashlib
6
+ import json
7
+ import os
8
+ import warnings
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+
13
+ EVENT_SCHEMA_VERSION = 1
14
+ IDEMPOTENT_DUPLICATE_EVENT_TYPES = {
15
+ "creator_endpoint_health_checked",
16
+ }
17
+
18
+
19
+ class JobEventError(RuntimeError):
20
+ pass
21
+
22
+
23
+ @dataclasses.dataclass(frozen=True)
24
+ class JobEvent:
25
+ schema_version: int
26
+ seq: int
27
+ event_id: str
28
+ timestamp: str
29
+ job_id: str
30
+ type: str
31
+ payload: dict[str, Any]
32
+
33
+ def to_json(self) -> dict[str, Any]:
34
+ return dataclasses.asdict(self)
35
+
36
+
37
+ def now_iso() -> str:
38
+ return dt.datetime.now(dt.timezone.utc).isoformat()
39
+
40
+
41
+ def event_id(seq: int) -> str:
42
+ return f"evt-{seq:06d}"
43
+
44
+
45
+ def validate_event(data: dict[str, Any]) -> JobEvent:
46
+ required = {"schema_version", "seq", "event_id", "timestamp", "job_id", "type", "payload"}
47
+ missing = required - set(data)
48
+ if missing:
49
+ raise JobEventError(f"event missing required fields: {sorted(missing)}")
50
+ if data["schema_version"] != EVENT_SCHEMA_VERSION:
51
+ raise JobEventError(f"schema_migration_required: {data['schema_version']}")
52
+ if not isinstance(data["seq"], int) or data["seq"] < 1:
53
+ raise JobEventError("event seq must be a positive integer")
54
+ if data["event_id"] != event_id(data["seq"]):
55
+ raise JobEventError(f"event_id does not match seq: {data['event_id']} vs {event_id(data['seq'])}")
56
+ if not isinstance(data["payload"], dict):
57
+ raise JobEventError("event payload must be an object")
58
+ return JobEvent(
59
+ schema_version=data["schema_version"],
60
+ seq=data["seq"],
61
+ event_id=data["event_id"],
62
+ timestamp=str(data["timestamp"]),
63
+ job_id=str(data["job_id"]),
64
+ type=str(data["type"]),
65
+ payload=data["payload"],
66
+ )
67
+
68
+
69
+ def read_events(path: Path) -> list[JobEvent]:
70
+ if not path.exists():
71
+ return []
72
+ events: list[JobEvent] = []
73
+ previous_seq = 0
74
+ previous_fingerprint: str | None = None
75
+ text = path.read_text(encoding="utf-8")
76
+ lines = text.splitlines()
77
+ final_line_may_be_torn = bool(lines) and not text.endswith("\n")
78
+ for line_no, line in enumerate(lines, start=1):
79
+ if not line.strip():
80
+ continue
81
+ try:
82
+ raw = json.loads(line)
83
+ except json.JSONDecodeError as exc:
84
+ if final_line_may_be_torn and line_no == len(lines):
85
+ warnings.warn(f"dropping torn final event line in {path}: {exc}", RuntimeWarning)
86
+ break
87
+ raise JobEventError(f"invalid JSON at line {line_no}: {exc}") from exc
88
+ event = validate_event(raw)
89
+ if event.seq != previous_seq + 1:
90
+ raise JobEventError(f"non-monotonic event seq at line {line_no}: {event.seq} after {previous_seq}")
91
+ fingerprint_payload = {
92
+ "job_id": event.job_id,
93
+ "type": event.type,
94
+ "payload": event.payload,
95
+ }
96
+ fingerprint = hashlib.sha256(json.dumps(fingerprint_payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode("utf-8")).hexdigest()
97
+ if previous_fingerprint == fingerprint and event.type not in IDEMPOTENT_DUPLICATE_EVENT_TYPES:
98
+ warnings.warn(
99
+ f"adjacent duplicate event content fingerprint in {path}: line {line_no}",
100
+ RuntimeWarning,
101
+ )
102
+ previous_fingerprint = fingerprint
103
+ previous_seq = event.seq
104
+ events.append(event)
105
+ return events
106
+
107
+
108
+ class JobEventLog:
109
+ def __init__(self, path: Path, job_id: str):
110
+ self.path = path
111
+ self.job_id = job_id
112
+ self.path.parent.mkdir(parents=True, exist_ok=True)
113
+
114
+ def next_seq(self) -> int:
115
+ return len(read_events(self.path)) + 1
116
+
117
+ def append(self, event_type: str, payload: dict[str, Any] | None = None) -> JobEvent:
118
+ seq = self.next_seq()
119
+ event = JobEvent(
120
+ schema_version=EVENT_SCHEMA_VERSION,
121
+ seq=seq,
122
+ event_id=event_id(seq),
123
+ timestamp=now_iso(),
124
+ job_id=self.job_id,
125
+ type=event_type,
126
+ payload=payload or {},
127
+ )
128
+ with self.path.open("a", encoding="utf-8") as handle:
129
+ handle.write(json.dumps(event.to_json(), ensure_ascii=False) + "\n")
130
+ handle.flush()
131
+ os.fsync(handle.fileno())
132
+ return event
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import json
5
+ import os
6
+ import time
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+
11
+ class JobLockError(RuntimeError):
12
+ pass
13
+
14
+
15
+ @dataclasses.dataclass
16
+ class JobLock:
17
+ path: Path
18
+ stale_after_seconds: int = 24 * 60 * 60
19
+
20
+ def __enter__(self) -> JobLock:
21
+ self.acquire()
22
+ return self
23
+
24
+ def __exit__(self, exc_type: object, exc: object, tb: object) -> None:
25
+ self.release()
26
+
27
+ def acquire(self) -> None:
28
+ self.path.parent.mkdir(parents=True, exist_ok=True)
29
+ while True:
30
+ try:
31
+ fd = os.open(str(self.path), os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
32
+ except FileExistsError:
33
+ if self._remove_if_stale():
34
+ continue
35
+ raise JobLockError(f"job is already locked: {self.path}") from None
36
+ payload = {
37
+ "pid": os.getpid(),
38
+ "created_at": time.time(),
39
+ }
40
+ with os.fdopen(fd, "w", encoding="utf-8") as handle:
41
+ handle.write(json.dumps(payload, indent=2) + "\n")
42
+ handle.flush()
43
+ os.fsync(handle.fileno())
44
+ return
45
+
46
+ def release(self) -> None:
47
+ try:
48
+ payload = self._read_payload()
49
+ if int(payload.get("pid", -1)) == os.getpid():
50
+ self.path.unlink()
51
+ except FileNotFoundError:
52
+ return
53
+ except (OSError, ValueError):
54
+ return
55
+
56
+ def _read_payload(self) -> dict[str, Any]:
57
+ return json.loads(self.path.read_text(encoding="utf-8"))
58
+
59
+ def _remove_if_stale(self) -> bool:
60
+ try:
61
+ payload = self._read_payload()
62
+ pid = int(payload.get("pid", -1))
63
+ created_at = float(payload.get("created_at", 0))
64
+ except (OSError, ValueError, json.JSONDecodeError):
65
+ pid = -1
66
+ created_at = 0
67
+
68
+ if pid > 0 and self._pid_is_alive(pid):
69
+ return False
70
+
71
+ if created_at and time.time() - created_at < self.stale_after_seconds and pid > 0:
72
+ return False
73
+
74
+ try:
75
+ self.path.unlink()
76
+ return True
77
+ except FileNotFoundError:
78
+ return True
79
+ except OSError:
80
+ return False
81
+
82
+ @staticmethod
83
+ def _pid_is_alive(pid: int) -> bool:
84
+ try:
85
+ os.kill(pid, 0)
86
+ except ProcessLookupError:
87
+ return False
88
+ except PermissionError:
89
+ return True
90
+ return True