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.
- package/LICENSE +100 -0
- package/NOTICE +5 -0
- package/README.md +434 -0
- package/SECURITY.md +64 -0
- package/SOCRA_SOURCE.md +36 -0
- package/TRADEMARKS.md +12 -0
- package/bin/wydcode.js +74 -0
- package/examples/behavior-preference-card.json +12 -0
- package/examples/exact-recurrence-card.json +11 -0
- package/examples/heldout-gene-evidence.json +6 -0
- package/examples/humaneval-ornith.md +29 -0
- package/examples/large_context_json_diagnostic.py +187 -0
- package/fixtures/failing-node-repo/package.json +9 -0
- package/fixtures/failing-node-repo/src/math.js +3 -0
- package/fixtures/failing-node-repo/test/math.test.js +8 -0
- package/package.json +55 -0
- package/pyproject.toml +24 -0
- package/socra_harness/__init__.py +3 -0
- package/socra_harness/attempts.py +76 -0
- package/socra_harness/central_traces.py +627 -0
- package/socra_harness/cli.py +265 -0
- package/socra_harness/eval/__init__.py +1 -0
- package/socra_harness/eval/humaneval.py +388 -0
- package/socra_harness/events.py +39 -0
- package/socra_harness/init.py +206 -0
- package/socra_harness/job/__init__.py +2 -0
- package/socra_harness/job/events.py +132 -0
- package/socra_harness/job/lock.py +90 -0
- package/socra_harness/job/manager.py +3785 -0
- package/socra_harness/job/planner.py +253 -0
- package/socra_harness/job/state.py +106 -0
- package/socra_harness/mumpix_trace_bridge.cjs +32 -0
- package/socra_harness/product.py +533 -0
- package/socra_harness/recurrence.py +263 -0
- package/socra_harness/scan/__init__.py +1 -0
- package/socra_harness/scan/repo.py +85 -0
- package/socra_harness/serve/__init__.py +1 -0
- package/socra_harness/serve/local_proxy.py +293 -0
- package/socra_harness/start.py +331 -0
- package/socra_harness/tui/__init__.py +1 -0
- package/socra_harness/tui/app.py +1114 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import os
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .eval.humaneval import humaneval_main
|
|
9
|
+
from .init import init_main
|
|
10
|
+
from .job.manager import (
|
|
11
|
+
job_configure_creator_main,
|
|
12
|
+
job_exam_dayjs_main,
|
|
13
|
+
job_plan_main,
|
|
14
|
+
job_promote_main,
|
|
15
|
+
job_resume_main,
|
|
16
|
+
job_review_promotion_main,
|
|
17
|
+
job_reverify_task_main,
|
|
18
|
+
job_run_milestone_main,
|
|
19
|
+
job_run_next_main,
|
|
20
|
+
job_start_main,
|
|
21
|
+
)
|
|
22
|
+
from .product import approve_main, fix_main, promote_main, reject_main, review_main, status_main
|
|
23
|
+
from .scan.repo import scan_main
|
|
24
|
+
from .serve.local_proxy import serve_main
|
|
25
|
+
from .start import start_main
|
|
26
|
+
from .tui.app import tui_main
|
|
27
|
+
from .central_traces import traces_status_main, traces_sync_main
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def main(argv: list[str] | None = None) -> int:
|
|
31
|
+
parser = argparse.ArgumentParser(prog=os.environ.get("SOCRA_CLI_NAME", "wydcode"))
|
|
32
|
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
33
|
+
|
|
34
|
+
init = sub.add_parser("init", help="Initialize Socra config in a git repository")
|
|
35
|
+
init.add_argument("--project", type=Path, default=Path.cwd(), help="Git repository to initialize")
|
|
36
|
+
init.add_argument("--endpoint", default=None, help="OpenAI-compatible endpoint, defaults to SOCRA_ENDPOINT or http://127.0.0.1:11439/v1")
|
|
37
|
+
init.add_argument("--model", default=None, help="Model name to record, defaults to SOCRA_MODEL when set")
|
|
38
|
+
init.add_argument("--context-budget", type=int, default=8192)
|
|
39
|
+
init.add_argument("--max-output", type=int, default=8192)
|
|
40
|
+
init.add_argument("--skip-endpoint-probe", action="store_true")
|
|
41
|
+
init.add_argument("--force", action="store_true", help="Overwrite an existing .socra/config.json")
|
|
42
|
+
init.set_defaults(func=init_main)
|
|
43
|
+
|
|
44
|
+
status = sub.add_parser("status", help="Show initialized config and latest durable job state")
|
|
45
|
+
status.add_argument("--project", type=Path, default=Path.cwd(), help="Git repository to inspect")
|
|
46
|
+
status.set_defaults(func=status_main)
|
|
47
|
+
|
|
48
|
+
fix = sub.add_parser("fix", help="Create a durable fix job from .socra/config.json")
|
|
49
|
+
fix.add_argument("goal")
|
|
50
|
+
fix.add_argument("--project", type=Path, default=Path.cwd(), help="Git repository to work on")
|
|
51
|
+
fix.add_argument("--job-id", default=None)
|
|
52
|
+
fix.add_argument("--declared-file", action="append", default=[], help="Source file the first Creator attempt may edit; repeat for up to two files")
|
|
53
|
+
fix.add_argument("--recurrence-card", type=Path, default=None, help="Structured exact-recurrence card; missing or incomplete cards abstain")
|
|
54
|
+
fix.add_argument("--retrieval", choices=("exact", "disabled"), default="exact", help="Use exact-key retrieval or run the paired cold control")
|
|
55
|
+
fix.add_argument("--plan-only", action="store_true", help="Create the durable job without requiring a live model endpoint")
|
|
56
|
+
fix.set_defaults(func=fix_main)
|
|
57
|
+
|
|
58
|
+
review = sub.add_parser("review", help="Present the latest passed fix diff and record the presented artifact hashes")
|
|
59
|
+
review.add_argument("--project", type=Path, default=Path.cwd(), help="Git repository to inspect")
|
|
60
|
+
review.add_argument("--job-id", default=None)
|
|
61
|
+
review.add_argument("--task-id", default=None)
|
|
62
|
+
review.add_argument("--reviewer-name", default=None)
|
|
63
|
+
review.add_argument("--notes", default="")
|
|
64
|
+
review.set_defaults(func=review_main)
|
|
65
|
+
|
|
66
|
+
approve = sub.add_parser("approve", help="Authorize the previously presented promotion diff")
|
|
67
|
+
approve.add_argument("--project", type=Path, default=Path.cwd(), help="Git repository to approve")
|
|
68
|
+
approve.add_argument("--job-id", default=None)
|
|
69
|
+
approve.add_argument("--task-id", default=None)
|
|
70
|
+
approve.add_argument("--reviewer-name", default=None)
|
|
71
|
+
approve.add_argument("--notes", default="")
|
|
72
|
+
approve.add_argument("--behavior-card", type=Path, default=None, help="Reviewer-authored PREFER behavior to bind to a validated promotion")
|
|
73
|
+
approve.add_argument("--heldout-evidence", type=Path, default=None, help="Passing held-out evidence card required when minting behavior")
|
|
74
|
+
approve.set_defaults(func=approve_main)
|
|
75
|
+
|
|
76
|
+
reject = sub.add_parser("reject", help="Reject the previously presented promotion diff and export the failed attempt trace")
|
|
77
|
+
reject.add_argument("--project", type=Path, default=Path.cwd(), help="Git repository containing the staged fix")
|
|
78
|
+
reject.add_argument("--job-id", default=None)
|
|
79
|
+
reject.add_argument("--task-id", default=None)
|
|
80
|
+
reject.add_argument("--reviewer-name", default=None)
|
|
81
|
+
reject.add_argument("--reason", required=True, help="Human-readable technical reason for rejecting the presented diff")
|
|
82
|
+
reject.add_argument("--notes", default="")
|
|
83
|
+
reject.set_defaults(func=reject_main)
|
|
84
|
+
|
|
85
|
+
promote = sub.add_parser("promote", help="Apply the approved promotion bundle to the real tree")
|
|
86
|
+
promote.add_argument("--project", type=Path, default=Path.cwd(), help="Git repository to promote into")
|
|
87
|
+
promote.add_argument("--job-id", default=None)
|
|
88
|
+
promote.add_argument("--task-id", default=None)
|
|
89
|
+
promote.add_argument("--preflight", action="store_true", help="Check authorization and bundle hashes without writing source")
|
|
90
|
+
promote.set_defaults(func=promote_main)
|
|
91
|
+
|
|
92
|
+
traces = sub.add_parser("traces", help="Inspect or repair the cross-project validated-fix trace store")
|
|
93
|
+
traces_sub = traces.add_subparsers(dest="traces_cmd", required=True)
|
|
94
|
+
traces_status = traces_sub.add_parser("status", help="Show global trace counts and hash-chain head")
|
|
95
|
+
traces_status.add_argument("--json", action="store_true")
|
|
96
|
+
traces_status.set_defaults(func=traces_status_main)
|
|
97
|
+
traces_sync = traces_sub.add_parser("sync", help="Backfill promoted fixes from this repository's local job logs")
|
|
98
|
+
traces_sync.add_argument("--project", type=Path, default=Path.cwd())
|
|
99
|
+
traces_sync.set_defaults(func=traces_sync_main)
|
|
100
|
+
|
|
101
|
+
start = sub.add_parser("start", help="Pick a model and project, then start a Socra vibe-coding session")
|
|
102
|
+
start.add_argument("--model", type=Path, default=None)
|
|
103
|
+
start.add_argument("--model-dir", action="append", default=[])
|
|
104
|
+
start.add_argument("--project", type=Path, default=None)
|
|
105
|
+
start.add_argument("--alias", default=None)
|
|
106
|
+
start.add_argument("--llama-server", type=Path, default=Path("llama-server"))
|
|
107
|
+
start.add_argument("--port", type=int, default=11439)
|
|
108
|
+
start.add_argument("--backend-port", type=int, default=11440)
|
|
109
|
+
start.add_argument("--ctx-size", type=int, default=8192)
|
|
110
|
+
start.add_argument("--gpu-layers", type=int, default=999)
|
|
111
|
+
start.add_argument("--no-scan", action="store_true")
|
|
112
|
+
start.add_argument("--no-ui", action="store_true", help="Do not open the terminal UI; block in server mode instead")
|
|
113
|
+
start.add_argument("--ui-refresh", type=float, default=1.0)
|
|
114
|
+
start.add_argument("--allow-sandbox-network", action="store_true", help="Allow agent-requested network commands inside .socra/run-sandbox only")
|
|
115
|
+
start.add_argument("--allow-sandbox-installs", action="store_true", help="Allow agent-requested install commands inside .socra/run-sandbox only")
|
|
116
|
+
start.set_defaults(func=start_main)
|
|
117
|
+
|
|
118
|
+
job = sub.add_parser("job", help="Manage durable long-running Socra jobs")
|
|
119
|
+
job_sub = job.add_subparsers(dest="job_cmd", required=True)
|
|
120
|
+
job_start = job_sub.add_parser("start", help="Create a durable job plan and event log without executing agents")
|
|
121
|
+
job_start.add_argument("goal")
|
|
122
|
+
job_start.add_argument("--project", type=Path, default=Path.cwd())
|
|
123
|
+
job_start.add_argument("--job-id", default=None)
|
|
124
|
+
job_start.set_defaults(func=job_start_main)
|
|
125
|
+
|
|
126
|
+
job_resume = job_sub.add_parser("resume", help="Replay events and resume an incomplete durable job")
|
|
127
|
+
job_resume.add_argument("--project", type=Path, default=Path.cwd())
|
|
128
|
+
job_resume.add_argument("--job-id", default=None)
|
|
129
|
+
job_resume.set_defaults(func=job_resume_main)
|
|
130
|
+
|
|
131
|
+
job_plan = job_sub.add_parser("plan", help="Emit planner task_added events and commit a hashed task DAG")
|
|
132
|
+
job_plan.add_argument("--project", type=Path, default=Path.cwd())
|
|
133
|
+
job_plan.add_argument("--job-id", default=None)
|
|
134
|
+
job_plan.add_argument("--force", action="store_true", help="Append a revised plan even when a plan is already committed")
|
|
135
|
+
job_plan.add_argument("--reason", default=None, help="Reason to record when --force appends a revised plan")
|
|
136
|
+
job_plan.set_defaults(func=job_plan_main)
|
|
137
|
+
|
|
138
|
+
job_creator = job_sub.add_parser("configure-creator", help="Pin Creator model/backend settings into the job event log")
|
|
139
|
+
job_creator.add_argument("--project", type=Path, default=Path.cwd())
|
|
140
|
+
job_creator.add_argument("--job-id", default=None)
|
|
141
|
+
job_creator.add_argument("--model-alias", default="Socra-14B-Coder")
|
|
142
|
+
job_creator.add_argument("--model", default="Socra-14B-Coder.gguf")
|
|
143
|
+
job_creator.add_argument("--expected-served-model-id", default=None)
|
|
144
|
+
job_creator.add_argument("--endpoint", default="http://127.0.0.1:11439/v1")
|
|
145
|
+
job_creator.add_argument("--backend", default="local-openai-compatible")
|
|
146
|
+
job_creator.add_argument("--temperature", type=float, default=0.7)
|
|
147
|
+
job_creator.add_argument("--seed", type=int, default=None)
|
|
148
|
+
job_creator.add_argument("--context-budget-tokens", type=int, default=8192)
|
|
149
|
+
job_creator.add_argument("--max-output-tokens", type=int, default=32768)
|
|
150
|
+
job_creator.add_argument("--hosted-fallback", action="store_true")
|
|
151
|
+
job_creator.add_argument("--notes", default=None)
|
|
152
|
+
job_creator.set_defaults(func=job_configure_creator_main)
|
|
153
|
+
|
|
154
|
+
job_exam_dayjs = job_sub.add_parser("exam-dayjs", help="Create a provenance-pinned dayjs ground-truth exam job")
|
|
155
|
+
job_exam_dayjs.add_argument("candidate", help="Candidate exam id, e.g. E1 or dayjs-2369-duration-zero-getter")
|
|
156
|
+
job_exam_dayjs.add_argument("--project", type=Path, default=Path.cwd(), help="Clean dayjs checkout at the candidate pre-fix parent")
|
|
157
|
+
job_exam_dayjs.add_argument("--job-id", default=None)
|
|
158
|
+
job_exam_dayjs.add_argument("--pack", type=Path, default=None, help="Path to dayjs_exam_set_v1.json")
|
|
159
|
+
job_exam_dayjs.add_argument("--prediction", default=None, help="Override the preregistered prediction recorded for this candidate")
|
|
160
|
+
job_exam_dayjs.add_argument("--prediction-notes", default="")
|
|
161
|
+
job_exam_dayjs.add_argument("--allow-checkout-mismatch", action="store_true", help="Record the job even if HEAD does not match the candidate pre-fix parent")
|
|
162
|
+
job_exam_dayjs.set_defaults(func=job_exam_dayjs_main)
|
|
163
|
+
|
|
164
|
+
job_run = job_sub.add_parser("run-next", help="Run the next ready task through the durable job event protocol")
|
|
165
|
+
job_run.add_argument("--project", type=Path, default=Path.cwd())
|
|
166
|
+
job_run.add_argument("--job-id", default=None)
|
|
167
|
+
job_run.add_argument("--task-id", default=None)
|
|
168
|
+
job_run.add_argument("--allow-unimplemented", action="store_true")
|
|
169
|
+
job_run.set_defaults(func=job_run_next_main)
|
|
170
|
+
|
|
171
|
+
job_reverify = job_sub.add_parser("reverify-task", help="Upgrade a smoke-reviewed task with criterion-level evidence")
|
|
172
|
+
job_reverify.add_argument("--project", type=Path, default=Path.cwd())
|
|
173
|
+
job_reverify.add_argument("--job-id", default=None)
|
|
174
|
+
job_reverify.add_argument("--task-id", required=True)
|
|
175
|
+
job_reverify.add_argument("--evidence", type=Path, required=True)
|
|
176
|
+
job_reverify.add_argument("--notes", default="")
|
|
177
|
+
job_reverify.add_argument("--allow-unpassed", action="store_true")
|
|
178
|
+
job_reverify.set_defaults(func=job_reverify_task_main)
|
|
179
|
+
|
|
180
|
+
job_milestone = job_sub.add_parser("run-milestone", help="Run a milestone gate against a sandbox overlay of passed task artifacts")
|
|
181
|
+
job_milestone.add_argument("--project", type=Path, default=Path.cwd())
|
|
182
|
+
job_milestone.add_argument("--job-id", default=None)
|
|
183
|
+
job_milestone.add_argument("--milestone-id", default=None)
|
|
184
|
+
job_milestone.add_argument("--command", default="npm run build")
|
|
185
|
+
job_milestone.add_argument("--timeout-seconds", type=int, default=120)
|
|
186
|
+
job_milestone.add_argument("--prepare-deps", choices=["none", "npm-ci", "npm-install"], default="none")
|
|
187
|
+
job_milestone.add_argument("--deps-timeout-seconds", type=int, default=300)
|
|
188
|
+
job_milestone.add_argument("--allow-partial-smoke", action="store_true", help="Run the command even when partial-smoke tasks still require explicit re-verification")
|
|
189
|
+
job_milestone.add_argument("--promotion-grade", action="store_true", help="Treat this milestone as promotion-grade; high/critical dependency vulnerabilities block it")
|
|
190
|
+
job_milestone.set_defaults(func=job_run_milestone_main)
|
|
191
|
+
|
|
192
|
+
job_review_promotion = job_sub.add_parser("review-promotion", help="Present a promotion diff and record human authorization")
|
|
193
|
+
job_review_promotion.add_argument("--project", type=Path, default=Path.cwd())
|
|
194
|
+
job_review_promotion.add_argument("--job-id", default=None)
|
|
195
|
+
job_review_promotion.add_argument("--task-id", default="task-045")
|
|
196
|
+
job_review_promotion.add_argument("--reviewer-name", required=True)
|
|
197
|
+
job_review_promotion.add_argument("--present-diff", action="store_true", help="Render the exact dry-run diff to stdout and record promotion_diff_presented; run separately before authorization")
|
|
198
|
+
job_review_promotion.add_argument("--approve-source-promotion", action="store_true", help="Human reviewer approves this dry-run bundle for real project promotion")
|
|
199
|
+
job_review_promotion.add_argument("--attest-reviewed", action="store_true", help="Record a self-attestation that the human reviewed the presented diff")
|
|
200
|
+
job_review_promotion.add_argument("--notes", default="")
|
|
201
|
+
job_review_promotion.set_defaults(func=job_review_promotion_main)
|
|
202
|
+
|
|
203
|
+
job_promote = job_sub.add_parser("promote", help="Preflight or execute a promotion bundle after human authorization")
|
|
204
|
+
job_promote.add_argument("--project", type=Path, default=Path.cwd())
|
|
205
|
+
job_promote.add_argument("--job-id", default=None)
|
|
206
|
+
job_promote.add_argument("--task-id", default="task-045")
|
|
207
|
+
job_promote.add_argument("--execute", action="store_true", help="Apply the approved bundle with preimage checks, rollback snapshot, and post-write hash verification")
|
|
208
|
+
job_promote.set_defaults(func=job_promote_main)
|
|
209
|
+
|
|
210
|
+
eval_parser = sub.add_parser("eval", help="Run benchmark harnesses")
|
|
211
|
+
eval_sub = eval_parser.add_subparsers(dest="benchmark", required=True)
|
|
212
|
+
he = eval_sub.add_parser("humaneval", help="Run HumanEval against any GGUF")
|
|
213
|
+
he.add_argument("--model", type=Path, required=True)
|
|
214
|
+
he.add_argument("--name", default="model")
|
|
215
|
+
he.add_argument("--llama-cli", type=Path, default=Path("llama-cli"))
|
|
216
|
+
he.add_argument("--tasks", type=Path, required=True)
|
|
217
|
+
he.add_argument("--out", type=Path, default=Path(".socra/humaneval"))
|
|
218
|
+
he.add_argument("--report", type=Path, default=Path(".socra/humaneval/report.md"))
|
|
219
|
+
he.add_argument("--mode", choices=["raw-full-function", "socra"], default="socra")
|
|
220
|
+
he.add_argument("--limit", type=int, default=0)
|
|
221
|
+
he.add_argument("--n-predict", type=int, default=384)
|
|
222
|
+
he.add_argument("--temp", type=float, default=0.1)
|
|
223
|
+
he.add_argument("--timeout", type=int, default=10)
|
|
224
|
+
he.add_argument("--ctx-size", type=int, default=2048)
|
|
225
|
+
he.add_argument("--gpu-layers", type=int, default=None)
|
|
226
|
+
he.add_argument("--batch-size", type=int, default=128)
|
|
227
|
+
he.add_argument("--ubatch-size", type=int, default=64)
|
|
228
|
+
he.add_argument("--cache-type-k", default="q8_0")
|
|
229
|
+
he.add_argument("--cache-type-v", default="q8_0")
|
|
230
|
+
he.add_argument("--cache-ram-mb", type=int, default=2048)
|
|
231
|
+
he.add_argument("--no-cpu-fallback", action="store_true")
|
|
232
|
+
he.add_argument("--event-log", type=Path, default=Path(".socra/events.jsonl"))
|
|
233
|
+
he.set_defaults(func=humaneval_main)
|
|
234
|
+
|
|
235
|
+
serve = sub.add_parser("serve", help="Run local OpenAI-compatible GGUF proxy")
|
|
236
|
+
serve.add_argument("--model", type=Path, required=True)
|
|
237
|
+
serve.add_argument("--alias", default=None, help="Model name exposed through the Socra endpoint")
|
|
238
|
+
serve.add_argument("--llama-server", type=Path, default=Path("llama-server"))
|
|
239
|
+
serve.add_argument("--host", default="127.0.0.1")
|
|
240
|
+
serve.add_argument("--port", type=int, default=11439)
|
|
241
|
+
serve.add_argument("--backend-host", default="127.0.0.1")
|
|
242
|
+
serve.add_argument("--backend-port", type=int, default=11440)
|
|
243
|
+
serve.add_argument("--ctx-size", type=int, default=8192)
|
|
244
|
+
serve.add_argument("--gpu-layers", type=int, default=999)
|
|
245
|
+
serve.add_argument("--reuse-backend", action="store_true", help="Do not start llama-server; proxy an already-running local backend")
|
|
246
|
+
serve.add_argument("--event-log", type=Path, default=Path(".socra/events.jsonl"))
|
|
247
|
+
serve.set_defaults(func=serve_main)
|
|
248
|
+
|
|
249
|
+
scan = sub.add_parser("scan", help="Scan a repo for repeatable Socra candidates")
|
|
250
|
+
scan.add_argument("folder", type=Path)
|
|
251
|
+
scan.add_argument("--out", type=Path, default=Path(".socra/candidates.json"))
|
|
252
|
+
scan.add_argument("--event-log", type=Path, default=Path(".socra/events.jsonl"))
|
|
253
|
+
scan.set_defaults(func=scan_main)
|
|
254
|
+
|
|
255
|
+
tui = sub.add_parser("tui", help="Terminal dashboard")
|
|
256
|
+
tui.add_argument("--state-dir", type=Path, default=Path(".socra"))
|
|
257
|
+
tui.add_argument("--refresh", type=float, default=1.0)
|
|
258
|
+
tui.set_defaults(func=tui_main)
|
|
259
|
+
|
|
260
|
+
args = parser.parse_args(argv)
|
|
261
|
+
return args.func(args)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
if __name__ == "__main__":
|
|
265
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
import tempfile
|
|
9
|
+
import textwrap
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from ..events import EventLog
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
STOP_MARKERS = [
|
|
18
|
+
"\nclass ",
|
|
19
|
+
"\ndef ",
|
|
20
|
+
"\nif __name__",
|
|
21
|
+
"\n# Tests",
|
|
22
|
+
"\nassert ",
|
|
23
|
+
"\nprint(",
|
|
24
|
+
"\ncheck(",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def read_jsonl(path: Path) -> list[dict[str, Any]]:
|
|
29
|
+
return [json.loads(line) for line in path.read_text().splitlines() if line.strip()]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def run(cmd: list[str], timeout: int, cwd: Path | None = None, env: dict[str, str] | None = None) -> subprocess.CompletedProcess[str]:
|
|
33
|
+
return subprocess.run(cmd, text=True, capture_output=True, timeout=timeout, cwd=str(cwd) if cwd else None, env=env)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def generation_cmd(args: Any, prompt: str, cpu_only: bool) -> list[str]:
|
|
37
|
+
cmd = [
|
|
38
|
+
str(args.llama_cli),
|
|
39
|
+
"-m",
|
|
40
|
+
str(args.model),
|
|
41
|
+
"--single-turn",
|
|
42
|
+
"--reasoning",
|
|
43
|
+
"off",
|
|
44
|
+
"--no-display-prompt",
|
|
45
|
+
"--no-show-timings",
|
|
46
|
+
"--simple-io",
|
|
47
|
+
"-p",
|
|
48
|
+
prompt,
|
|
49
|
+
"-n",
|
|
50
|
+
str(args.n_predict),
|
|
51
|
+
"--temp",
|
|
52
|
+
str(args.temp),
|
|
53
|
+
"--ctx-size",
|
|
54
|
+
str(args.ctx_size),
|
|
55
|
+
"--batch-size",
|
|
56
|
+
str(args.batch_size),
|
|
57
|
+
"--ubatch-size",
|
|
58
|
+
str(args.ubatch_size),
|
|
59
|
+
]
|
|
60
|
+
if cpu_only:
|
|
61
|
+
cmd.extend(["--device", "none", "--gpu-layers", "0", "--no-op-offload"])
|
|
62
|
+
elif args.gpu_layers is not None:
|
|
63
|
+
cmd.extend(["--gpu-layers", str(args.gpu_layers)])
|
|
64
|
+
if args.cache_type_k:
|
|
65
|
+
cmd.extend(["--cache-type-k", args.cache_type_k])
|
|
66
|
+
if args.cache_type_v:
|
|
67
|
+
cmd.extend(["--cache-type-v", args.cache_type_v])
|
|
68
|
+
if args.cache_ram_mb is not None:
|
|
69
|
+
cmd.extend(["--cache-ram", str(args.cache_ram_mb)])
|
|
70
|
+
return cmd
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def generate(args: Any, prompt: str, raw_path: Path) -> tuple[str, float, str]:
|
|
74
|
+
start = time.time()
|
|
75
|
+
proc = run(generation_cmd(args, prompt, cpu_only=False), timeout=240)
|
|
76
|
+
mode = "gpu"
|
|
77
|
+
first_failure = proc
|
|
78
|
+
if not args.no_cpu_fallback and proc.returncode != 0 and (
|
|
79
|
+
"ggml_metal_init" in proc.stderr
|
|
80
|
+
or "failed to create command queue" in proc.stderr
|
|
81
|
+
or "failed to allocate context" in proc.stderr
|
|
82
|
+
):
|
|
83
|
+
proc = run(generation_cmd(args, prompt, cpu_only=True), timeout=360)
|
|
84
|
+
mode = "cpu_fallback"
|
|
85
|
+
elapsed = time.time() - start
|
|
86
|
+
raw_path.write_text(
|
|
87
|
+
f"--- MODE ---\n{mode}\n"
|
|
88
|
+
f"--- STDOUT ---\n{proc.stdout}\n"
|
|
89
|
+
f"--- STDERR ---\n{proc.stderr}"
|
|
90
|
+
+ (
|
|
91
|
+
""
|
|
92
|
+
if first_failure is proc
|
|
93
|
+
else f"\n--- FIRST_FAILURE_STDOUT ---\n{first_failure.stdout}\n"
|
|
94
|
+
f"--- FIRST_FAILURE_STDERR ---\n{first_failure.stderr}"
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
if proc.returncode != 0:
|
|
98
|
+
raise RuntimeError(f"generation failed with code {proc.returncode}: {proc.stderr[-1200:]}")
|
|
99
|
+
return proc.stdout, elapsed, mode
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def build_prompt(task: dict[str, Any], mode: str) -> str:
|
|
103
|
+
if mode == "raw-full-function":
|
|
104
|
+
return "\n".join(
|
|
105
|
+
[
|
|
106
|
+
"Write a complete, valid Python function that satisfies the docstring and examples.",
|
|
107
|
+
"Return only one Python function definition.",
|
|
108
|
+
"Do not include Markdown fences, explanations, tests, or extra text.",
|
|
109
|
+
"",
|
|
110
|
+
task["prompt"],
|
|
111
|
+
]
|
|
112
|
+
)
|
|
113
|
+
return "\n".join(
|
|
114
|
+
[
|
|
115
|
+
"Complete the following Python function.",
|
|
116
|
+
"Return only the code that should be appended after the prompt.",
|
|
117
|
+
"Do not include Markdown fences, explanations, tests, or the original prompt.",
|
|
118
|
+
"",
|
|
119
|
+
task["prompt"],
|
|
120
|
+
]
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def strip_llama_cli_scaffold(raw: str) -> str:
|
|
125
|
+
out = raw.replace("\r\n", "\n")
|
|
126
|
+
fence = re.search(r"```(?:python)?\s*([\s\S]*?)```", out, re.I)
|
|
127
|
+
fenced = bool(fence)
|
|
128
|
+
if fence:
|
|
129
|
+
out = fence.group(1)
|
|
130
|
+
out = re.sub(r"^.*?>\s*", "", out, count=1, flags=re.S) if not fenced and "\n>" in raw[:2000] else out
|
|
131
|
+
out = re.sub(r"(?is)<think>.*?</think>", "", out)
|
|
132
|
+
out = out.replace("<|endoftext|>", "").replace("Exiting...", "")
|
|
133
|
+
skip_prefixes = (
|
|
134
|
+
"Loading model",
|
|
135
|
+
"build :",
|
|
136
|
+
"model :",
|
|
137
|
+
"modalities :",
|
|
138
|
+
"available commands:",
|
|
139
|
+
"/exit",
|
|
140
|
+
"/regen",
|
|
141
|
+
"/clear",
|
|
142
|
+
"/read",
|
|
143
|
+
"/glob",
|
|
144
|
+
)
|
|
145
|
+
lines = []
|
|
146
|
+
for line in out.splitlines():
|
|
147
|
+
stripped = line.strip()
|
|
148
|
+
if not stripped:
|
|
149
|
+
lines.append(line)
|
|
150
|
+
continue
|
|
151
|
+
if any(stripped.startswith(prefix) for prefix in skip_prefixes):
|
|
152
|
+
continue
|
|
153
|
+
if "▄▄ ▄▄" in stripped or "██ ██" in stripped or "▀▀" in stripped:
|
|
154
|
+
continue
|
|
155
|
+
lines.append(line)
|
|
156
|
+
return "\n".join(lines).strip("\n")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def trim_at_stop_markers(out: str) -> str:
|
|
160
|
+
for marker in STOP_MARKERS:
|
|
161
|
+
index = out.find(marker)
|
|
162
|
+
if index >= 0:
|
|
163
|
+
return out[:index]
|
|
164
|
+
return out
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def strip_leading_docstring_closer(out: str) -> str:
|
|
168
|
+
lines = out.strip("\n").splitlines()
|
|
169
|
+
while lines and lines[0].strip() in {'"""', "'''"}:
|
|
170
|
+
lines = lines[1:]
|
|
171
|
+
return "\n".join(lines).strip("\n")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def reindent_function_body(out: str) -> str:
|
|
175
|
+
lines = [(" " + line if line.strip() else line) for line in out.strip("\n").splitlines()]
|
|
176
|
+
return "\n".join(lines).rstrip() + "\n"
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def remove_extra_prompt_indent(out: str) -> str:
|
|
180
|
+
lines = out.strip("\n").splitlines()
|
|
181
|
+
indents = [len(line) - len(line.lstrip(" ")) for line in lines if line.strip()]
|
|
182
|
+
positive = [indent for indent in indents if indent > 0]
|
|
183
|
+
if not indents or indents[0] != 0 or not positive:
|
|
184
|
+
return out
|
|
185
|
+
shift = min(positive)
|
|
186
|
+
shifted = []
|
|
187
|
+
for line in lines:
|
|
188
|
+
indent = len(line) - len(line.lstrip(" "))
|
|
189
|
+
shifted.append(line[min(indent, shift):] if indent > 0 else line)
|
|
190
|
+
return "\n".join(shifted).strip("\n")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def shift_left(out: str, spaces: int) -> str:
|
|
194
|
+
shifted = []
|
|
195
|
+
for line in out.strip("\n").splitlines():
|
|
196
|
+
indent = len(line) - len(line.lstrip(" "))
|
|
197
|
+
shifted.append(line[min(indent, spaces):] if indent else line)
|
|
198
|
+
return "\n".join(shifted).strip("\n")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def append_body_ast_score(prompt: str, candidate: str) -> int | None:
|
|
202
|
+
try:
|
|
203
|
+
tree = ast.parse(prompt.rstrip() + "\n" + candidate)
|
|
204
|
+
except SyntaxError:
|
|
205
|
+
return None
|
|
206
|
+
return sum(1 for _ in ast.walk(tree))
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def normalize_append_body(out: str, prompt: str) -> tuple[str, str]:
|
|
210
|
+
stripped = strip_leading_docstring_closer(out)
|
|
211
|
+
if not stripped.strip():
|
|
212
|
+
return stripped, "empty"
|
|
213
|
+
indents = [len(line) - len(line.lstrip(" ")) for line in stripped.splitlines() if line.strip()]
|
|
214
|
+
positive = [indent for indent in indents if indent > 0]
|
|
215
|
+
shift_values = sorted(set([0] + positive[:1] + ([min(positive)] if positive else [])))
|
|
216
|
+
bases = [
|
|
217
|
+
stripped.strip("\n"),
|
|
218
|
+
textwrap.dedent(stripped).strip("\n"),
|
|
219
|
+
textwrap.dedent(remove_extra_prompt_indent(stripped)).strip("\n"),
|
|
220
|
+
]
|
|
221
|
+
bases.extend(textwrap.dedent(shift_left(stripped, shift)).strip("\n") for shift in shift_values)
|
|
222
|
+
candidates = []
|
|
223
|
+
seen = set()
|
|
224
|
+
for base in bases:
|
|
225
|
+
candidate = reindent_function_body(base)
|
|
226
|
+
if candidate not in seen:
|
|
227
|
+
candidates.append(candidate)
|
|
228
|
+
seen.add(candidate)
|
|
229
|
+
scored = [
|
|
230
|
+
(score, index, candidate)
|
|
231
|
+
for index, candidate in enumerate(candidates)
|
|
232
|
+
if (score := append_body_ast_score(prompt, candidate)) is not None
|
|
233
|
+
]
|
|
234
|
+
if scored:
|
|
235
|
+
_score, index, candidate = max(scored, key=lambda item: (item[0], -item[1]))
|
|
236
|
+
return candidate, f"syntax_repair_variant_{index}"
|
|
237
|
+
return (candidates[0] if candidates else stripped.rstrip() + "\n"), "syntax_unverified"
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def clean_completion(raw: str, prompt: str, entry_point: str, mode: str) -> tuple[str, str]:
|
|
241
|
+
out = strip_llama_cli_scaffold(raw)
|
|
242
|
+
if mode == "raw-full-function":
|
|
243
|
+
full_defs = list(re.finditer(rf"(?m)^def\s+{re.escape(entry_point)}\s*\(", out))
|
|
244
|
+
if full_defs:
|
|
245
|
+
out = out[full_defs[-1].start():]
|
|
246
|
+
return trim_at_stop_markers(out).rstrip() + "\n", "full_function_extract"
|
|
247
|
+
if prompt and prompt in out:
|
|
248
|
+
out = out.split(prompt, 1)[-1]
|
|
249
|
+
prompt_tail = prompt.strip().splitlines()[-1] if prompt.strip() else ""
|
|
250
|
+
if prompt_tail and prompt_tail in out:
|
|
251
|
+
out = out.split(prompt_tail, 1)[-1]
|
|
252
|
+
if "(truncated)" in out:
|
|
253
|
+
out = out.split("(truncated)", 1)[-1]
|
|
254
|
+
out = trim_at_stop_markers(out)
|
|
255
|
+
full_defs = list(re.finditer(rf"(?m)^def\s+{re.escape(entry_point)}\s*\(", out))
|
|
256
|
+
if full_defs:
|
|
257
|
+
return out[full_defs[-1].start():].rstrip() + "\n", "full_function_extract"
|
|
258
|
+
while True:
|
|
259
|
+
stripped = out.lstrip("\n")
|
|
260
|
+
first = stripped.splitlines()[0].strip() if stripped.splitlines() else ""
|
|
261
|
+
if first.startswith((">>>", ">>")) or first in {"True", "False"} or re.fullmatch(r"[-+]?\d+(?:\.\d+)?", first or ""):
|
|
262
|
+
out = "\n".join(stripped.splitlines()[1:])
|
|
263
|
+
continue
|
|
264
|
+
break
|
|
265
|
+
if mode == "socra":
|
|
266
|
+
return normalize_append_body(out, prompt)
|
|
267
|
+
return out.rstrip() + "\n", "raw_extract"
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def candidate_source(task: dict[str, Any], completion: str) -> str:
|
|
271
|
+
entry = task["entry_point"]
|
|
272
|
+
if re.search(rf"(?m)^def\s+{re.escape(entry)}\s*\(", completion):
|
|
273
|
+
imports = "\n".join(
|
|
274
|
+
line for line in task["prompt"].splitlines()
|
|
275
|
+
if re.match(r"^\s*(?:from\s+\S+\s+import\s+.+|import\s+.+)\s*$", line)
|
|
276
|
+
)
|
|
277
|
+
return (imports.rstrip() + "\n\n" if imports.strip() else "") + completion
|
|
278
|
+
return task["prompt"].rstrip() + "\n" + completion
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def sandbox_env(tmpdir: Path) -> dict[str, str]:
|
|
282
|
+
return {
|
|
283
|
+
"HOME": str(tmpdir),
|
|
284
|
+
"TMPDIR": str(tmpdir),
|
|
285
|
+
"PYTHONDONTWRITEBYTECODE": "1",
|
|
286
|
+
"PATH": os.environ.get("PATH", ""),
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def evaluate(task: dict[str, Any], completion: str, tmpdir: Path, timeout: int) -> tuple[bool, str]:
|
|
291
|
+
source = candidate_source(task, completion)
|
|
292
|
+
test = "\n".join([source, "", task["test"], "", f"check({task['entry_point']})", ""])
|
|
293
|
+
path = tmpdir / f"{task['task_id'].replace('/', '_')}.py"
|
|
294
|
+
path.write_text(test)
|
|
295
|
+
proc = run(["python3", str(path)], timeout=timeout, cwd=tmpdir, env=sandbox_env(tmpdir))
|
|
296
|
+
return proc.returncode == 0, (proc.stdout + proc.stderr)[-4000:]
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def markdown(report: dict[str, Any]) -> str:
|
|
300
|
+
summary = report["summary"]
|
|
301
|
+
lines = [
|
|
302
|
+
"# Socra Harness HumanEval Run",
|
|
303
|
+
"",
|
|
304
|
+
f"Model: `{report['model_name']}`",
|
|
305
|
+
f"Mode: `{report['mode']}`",
|
|
306
|
+
f"Task file: `{report['task_file']}`",
|
|
307
|
+
f"Task count: `{report['task_count']}`",
|
|
308
|
+
"",
|
|
309
|
+
"| Passed | Pass@1 | GPU generations | CPU fallback |",
|
|
310
|
+
"| ---: | ---: | ---: | ---: |",
|
|
311
|
+
f"| {summary['passed']}/{summary['total']} | {summary['pass_at_1']:.4f} | {summary['gpu_generations']} | {summary['cpu_fallback_generations']} |",
|
|
312
|
+
"",
|
|
313
|
+
"## Failures",
|
|
314
|
+
"",
|
|
315
|
+
]
|
|
316
|
+
failed = [row["task_id"] for row in report["results"] if not row["passed"]]
|
|
317
|
+
lines.extend([f"- `{task}`" for task in failed] or ["- none"])
|
|
318
|
+
return "\n".join(lines) + "\n"
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def humaneval_main(args: Any) -> int:
|
|
322
|
+
args.out.mkdir(parents=True, exist_ok=True)
|
|
323
|
+
args.report.parent.mkdir(parents=True, exist_ok=True)
|
|
324
|
+
events = EventLog(args.event_log)
|
|
325
|
+
tasks = read_jsonl(args.tasks)
|
|
326
|
+
if args.limit:
|
|
327
|
+
tasks = tasks[: args.limit]
|
|
328
|
+
rows = []
|
|
329
|
+
events.emit("eval_start", benchmark="humaneval", model=args.name, mode=args.mode, total=len(tasks))
|
|
330
|
+
with tempfile.TemporaryDirectory(prefix=f"socra-humaneval-{args.name}-") as td:
|
|
331
|
+
tmpdir = Path(td)
|
|
332
|
+
for index, task in enumerate(tasks, start=1):
|
|
333
|
+
safe_id = task["task_id"].replace("/", "_")
|
|
334
|
+
print(f"[{args.name}] {index}/{len(tasks)} {task['task_id']}", flush=True)
|
|
335
|
+
raw_path = args.out / f"{args.name}-{safe_id}.raw.txt"
|
|
336
|
+
completion_path = args.out / f"{args.name}-{safe_id}.completion.py"
|
|
337
|
+
started = time.time()
|
|
338
|
+
try:
|
|
339
|
+
raw, gen_seconds, generation_mode = generate(args, build_prompt(task, args.mode), raw_path)
|
|
340
|
+
completion, repair = clean_completion(raw, task["prompt"], task["entry_point"], args.mode)
|
|
341
|
+
completion_path.write_text(completion)
|
|
342
|
+
passed, detail = evaluate(task, completion, tmpdir, args.timeout)
|
|
343
|
+
except Exception as exc:
|
|
344
|
+
gen_seconds = time.time() - started
|
|
345
|
+
generation_mode = "error"
|
|
346
|
+
repair = "error"
|
|
347
|
+
completion_path.write_text("")
|
|
348
|
+
passed = False
|
|
349
|
+
detail = repr(exc)
|
|
350
|
+
row = {
|
|
351
|
+
"model": args.name,
|
|
352
|
+
"task_id": task["task_id"],
|
|
353
|
+
"entry_point": task["entry_point"],
|
|
354
|
+
"passed": passed,
|
|
355
|
+
"generation_mode": generation_mode,
|
|
356
|
+
"repair": repair,
|
|
357
|
+
"gen_seconds": round(gen_seconds, 3),
|
|
358
|
+
"completion_path": str(completion_path),
|
|
359
|
+
"raw_path": str(raw_path),
|
|
360
|
+
"detail": detail,
|
|
361
|
+
}
|
|
362
|
+
rows.append(row)
|
|
363
|
+
events.emit("eval_task", index=index, total=len(tasks), task_id=task["task_id"], passed=passed, repair=repair)
|
|
364
|
+
passed = sum(1 for row in rows if row["passed"])
|
|
365
|
+
summary = {
|
|
366
|
+
"passed": passed,
|
|
367
|
+
"total": len(rows),
|
|
368
|
+
"pass_at_1": passed / len(rows) if rows else 0,
|
|
369
|
+
"gpu_generations": sum(1 for row in rows if row["generation_mode"] == "gpu"),
|
|
370
|
+
"cpu_fallback_generations": sum(1 for row in rows if row["generation_mode"] == "cpu_fallback"),
|
|
371
|
+
}
|
|
372
|
+
report = {
|
|
373
|
+
"benchmark": "HumanEval",
|
|
374
|
+
"mode": args.mode,
|
|
375
|
+
"model_name": args.name,
|
|
376
|
+
"model_path": str(args.model),
|
|
377
|
+
"task_file": str(args.tasks),
|
|
378
|
+
"task_count": len(rows),
|
|
379
|
+
"summary": summary,
|
|
380
|
+
"results": rows,
|
|
381
|
+
}
|
|
382
|
+
(args.out / "results.json").write_text(json.dumps(report, indent=2, sort_keys=True) + "\n")
|
|
383
|
+
args.report.write_text(markdown(report))
|
|
384
|
+
events.emit("eval_complete", benchmark="humaneval", model=args.name, mode=args.mode, **summary)
|
|
385
|
+
print(json.dumps(summary, indent=2, sort_keys=True))
|
|
386
|
+
print(f"Results: {args.out / 'results.json'}")
|
|
387
|
+
print(f"Report: {args.report}")
|
|
388
|
+
return 0
|