devlyn-cli 3.1.0 → 3.1.1

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 (28) hide show
  1. package/README.md +6 -1
  2. package/bin/devlyn.js +69 -23
  3. package/config/skills/_shared/adapters/claude.md +3 -2
  4. package/config/skills/_shared/adapters/codex.md +1 -1
  5. package/config/skills/_shared/archive_run.py +5 -3
  6. package/config/skills/_shared/codex-config.md +13 -8
  7. package/config/skills/_shared/codex-monitored.sh +12 -19
  8. package/config/skills/_shared/collect-codex-findings.py +1 -0
  9. package/config/skills/_shared/engine-preflight.md +2 -0
  10. package/config/skills/_shared/finish-gate.py +4 -1
  11. package/config/skills/_shared/invocation-receipt.py +194 -5
  12. package/config/skills/_shared/judge-role-evidence.py +33 -6
  13. package/config/skills/_shared/phase-prompt-render.py +2 -0
  14. package/config/skills/_shared/platform-support.py +370 -0
  15. package/config/skills/_shared/process-evidence.py +4 -2
  16. package/config/skills/_shared/resolve-bootstrap.py +102 -59
  17. package/config/skills/_shared/resolve-stop-hook.py +3 -1
  18. package/config/skills/_shared/role-config.py +7 -5
  19. package/config/skills/_shared/run-bounded.py +35 -21
  20. package/config/skills/_shared/spec-verify-check.py +274 -157
  21. package/config/skills/_shared/state-phase-write.py +26 -9
  22. package/config/skills/_shared/task-complete.py +87 -83
  23. package/config/skills/_shared/verify-merge-findings.py +9 -8
  24. package/config/skills/devlyn:resolve/SKILL.md +10 -7
  25. package/config/skills/devlyn:resolve/references/phases/verify.md +5 -3
  26. package/config/skills/devlyn:resolve/references/task-completion.md +1 -1
  27. package/package.json +1 -1
  28. package/scripts/lint-skills.sh +6 -5
@@ -2,6 +2,8 @@
2
2
  """Create and validate run-owned Codex invocation receipts."""
3
3
  from __future__ import annotations
4
4
 
5
+ import runpy
6
+
5
7
  import argparse
6
8
  import hashlib
7
9
  import json
@@ -11,6 +13,7 @@ import re
11
13
  import signal
12
14
  import shutil
13
15
  import subprocess
16
+ import sys
14
17
  import tempfile
15
18
  import time
16
19
 
@@ -21,6 +24,7 @@ SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
21
24
  SAFE_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$")
22
25
  DANGEROUS_FLAGS = {"--dangerously-bypass-approvals-and-sandbox", "--yolo"}
23
26
  NETWORK_ACCESS_CONFIG = "sandbox_workspace_write.network_access"
27
+ PLATFORM = runpy.run_path(pathlib.Path(__file__).with_name("platform-support.py"))
24
28
 
25
29
 
26
30
  class ReceiptError(ValueError):
@@ -121,6 +125,95 @@ def sandbox_network_access(argv: list[str], phase: str) -> bool:
121
125
  return enabled
122
126
 
123
127
 
128
+ def file_prompt_args(argv):
129
+ """File mode accepts one explicit stdin prompt and no positional competitors."""
130
+ values = {"-C", "--cd", "-s", "--sandbox", "-m", "--model", "-c", "--config",
131
+ "-o", "--output-last-message", "--output-schema", "--color", "--enable",
132
+ "--disable", "--add-dir"}
133
+ flags = {"--json", "--ignore-user-config", "--ignore-rules", "--ephemeral",
134
+ "--skip-git-repo-check", "--full-auto"}
135
+ positional = []
136
+ index = 0
137
+ while index < len(argv):
138
+ token = argv[index]
139
+ if token in values:
140
+ if index + 1 >= len(argv):
141
+ raise ReceiptError(f"{token} requires a value")
142
+ index += 2
143
+ elif any(token.startswith(key + "=") for key in values) or token in flags:
144
+ index += 1
145
+ elif token == "--":
146
+ positional.extend(argv[index + 1:])
147
+ break
148
+ elif token == "-" or not token.startswith("-"):
149
+ positional.append(token)
150
+ index += 1
151
+ else:
152
+ raise ReceiptError(f"unsupported Codex file-transport option: {token}")
153
+ if positional != ["-"]:
154
+ raise ReceiptError("file transport requires the sole prompt argument '-'; competing prompts are forbidden")
155
+
156
+
157
+ def prepare_transport(prompt_path, command, argv, seconds, *, isolated=False):
158
+ path = pathlib.Path(prompt_path).resolve(strict=True)
159
+ with PLATFORM["open_stdin"](path) as source:
160
+ raw = source.read()
161
+ record = {"schema_version": 1, "transport": "stdin-file",
162
+ "prompt": {"path": str(path), "sha256": sha256(raw), "bytes": len(raw)},
163
+ "command": command, "argv": argv, "timeout_sec": seconds, "isolated": isolated,
164
+ "status": "started", "exit_code": None}
165
+ carrier = path.with_name(path.name + ".transport.json")
166
+ if carrier.exists():
167
+ raise ReceiptError(f"prompt transport already exists: {carrier}")
168
+ stream = tempfile.TemporaryFile("w+b")
169
+ try:
170
+ stream.write(raw)
171
+ stream.seek(0)
172
+ except BaseException:
173
+ stream.close()
174
+ raise
175
+ return stream, carrier, record
176
+
177
+
178
+ def write_transport(path, record):
179
+ with path.open("xb") as stream:
180
+ stream.write((json.dumps(record, sort_keys=True) + "\n").encode("utf-8"))
181
+
182
+
183
+ def finish_transport(path, exit_code):
184
+ record = read_receipt(path)
185
+ if record.get("status") != "started":
186
+ raise ReceiptError("prompt transport is not open")
187
+ record.update(status="completed", exit_code=exit_code)
188
+ atomic_write(path, record)
189
+
190
+
191
+ def validate_transport(path, prompt_raw):
192
+ record = read_receipt(path)
193
+ if set(record) != {"schema_version", "transport", "prompt", "command", "argv", "timeout_sec", "isolated", "status", "exit_code"}:
194
+ raise ReceiptError("invalid prompt transport shape")
195
+ prompt = record["prompt"]
196
+ if not isinstance(prompt, dict) or set(prompt) != {"path", "sha256", "bytes"}:
197
+ raise ReceiptError("invalid delivered prompt binding")
198
+ if (record["schema_version"] != 1 or record["transport"] != "stdin-file"
199
+ or record["status"] != "completed" or type(record["exit_code"]) is not int
200
+ or type(record["timeout_sec"]) is not int or record["timeout_sec"] < 0
201
+ or type(record["isolated"]) is not bool
202
+ or prompt["sha256"] != sha256(prompt_raw) or type(prompt["bytes"]) is not int
203
+ or prompt["bytes"] != len(prompt_raw)):
204
+ raise ReceiptError("delivered prompt/transport mismatch")
205
+ original = pathlib.Path(prompt["path"])
206
+ # Custody may relocate this sealed bundle; rehash its local prompt, not a live source tree.
207
+ if (not original.is_absolute() or path.name != original.name + ".transport.json"
208
+ or path.with_name(original.name).read_bytes() != prompt_raw):
209
+ raise ReceiptError("transport prompt path/bytes mismatch")
210
+ command, actual = record["command"], record["argv"]
211
+ if not all(isinstance(args, list) and args and all(isinstance(arg, str) for arg in args)
212
+ for args in (command, actual)) or len(actual) < len(command) or actual[len(actual) - len(command) + 1:] != command[1:]:
213
+ raise ReceiptError("transport actual argv mismatch")
214
+ return record
215
+
216
+
124
217
  def start_receipt(
125
218
  work: pathlib.Path,
126
219
  receipt_path: pathlib.Path,
@@ -130,6 +223,7 @@ def start_receipt(
130
223
  prompt_path: str,
131
224
  session_path: str,
132
225
  argv: list[str],
226
+ *, transport: dict | None = None,
133
227
  ) -> None:
134
228
  if receipt_path.exists():
135
229
  raise ReceiptError(f"invocation receipt already exists: {receipt_path}")
@@ -205,7 +299,12 @@ def start_receipt(
205
299
  prompt_argument = prompt_raw.decode("utf-8").rstrip("\n")
206
300
  except UnicodeError as exc:
207
301
  raise ReceiptError("invocation prompt is not UTF-8") from exc
208
- if not argv or argv[-1] != prompt_argument:
302
+ if transport is not None:
303
+ file_prompt_args(argv)
304
+ if (transport["prompt"] != {"path": str(prompt_file), "sha256": sha256(prompt_raw), "bytes": len(prompt_raw)}
305
+ or transport["command"][1:] != ["exec", *argv]):
306
+ raise ReceiptError("delivered prompt file/bytes or actual argv does not match the canonical invocation")
307
+ elif not argv or argv[-1] == "-" or argv[-1] != prompt_argument:
209
308
  raise ReceiptError("Codex prompt argument does not match the canonical prompt file")
210
309
 
211
310
  atomic_write(receipt_file, {
@@ -222,6 +321,7 @@ def start_receipt(
222
321
  "argv_sha256": sha256(json.dumps(argv, separators=(",", ":")).encode("utf-8")),
223
322
  "status": "started",
224
323
  "exit_code": None,
324
+ **({"transport": {"path": prompt_relative + ".transport.json", "sha256": None}} if transport is not None else {}),
225
325
  })
226
326
 
227
327
 
@@ -252,6 +352,9 @@ def finish_receipt(work: pathlib.Path, receipt_path: pathlib.Path, exit_code: in
252
352
  }
253
353
  receipt["status"] = "completed"
254
354
  receipt["exit_code"] = exit_code
355
+ if "transport" in receipt:
356
+ carrier, _ = relative_file(work, receipt["transport"]["path"], "prompt transport", require=True)
357
+ receipt["transport"]["sha256"] = sha256(carrier.read_bytes())
255
358
  atomic_write(receipt_path, receipt)
256
359
 
257
360
 
@@ -267,7 +370,7 @@ def validate_receipt_artifacts(
267
370
  work, str(receipt_path), "invocation receipt", require=True,
268
371
  )
269
372
  receipt = read_receipt(receipt_file)
270
- if set(receipt) != {
373
+ if set(receipt) - {"transport"} != {
271
374
  "schema_version", "run_id", "phase", "round", "engine", "model",
272
375
  "sandbox", "sandbox_network_access", "prompt", "session", "argv_sha256",
273
376
  "status", "exit_code",
@@ -309,6 +412,22 @@ def validate_receipt_artifacts(
309
412
  or sha256(prompt_file.read_bytes()) != prompt["sha256"]
310
413
  ):
311
414
  raise ReceiptError("invocation prompt digest mismatch")
415
+ if "transport" in receipt:
416
+ binding = receipt["transport"]
417
+ if not isinstance(binding, dict) or set(binding) != {"path", "sha256"} or binding["path"] != prompt["path"] + ".transport.json":
418
+ raise ReceiptError("invocation transport binding is invalid")
419
+ carrier, _ = relative_file(work, binding["path"], "prompt transport", require=True)
420
+ if sha256(carrier.read_bytes()) != binding["sha256"]:
421
+ raise ReceiptError("invocation transport digest mismatch")
422
+ transport = validate_transport(carrier, prompt_file.read_bytes())
423
+ argv = transport["command"][2:]
424
+ file_prompt_args(argv)
425
+ if (transport["command"][1:2] != ["exec"] or transport["exit_code"] != receipt["exit_code"]
426
+ or sha256(json.dumps(argv, separators=(",", ":")).encode("utf-8")) != receipt["argv_sha256"]
427
+ or option_value(argv, "-m", "--model") != receipt["model"]
428
+ or option_value(argv, "-s", "--sandbox") != receipt["sandbox"]
429
+ or sandbox_network_access(argv, phase) != receipt["sandbox_network_access"]):
430
+ raise ReceiptError("invocation actual argv/capability mismatch")
312
431
  session = receipt["session"]
313
432
  if not isinstance(session, dict) or set(session) != {"path", "sha256", "bytes"}:
314
433
  raise ReceiptError("invocation receipt session binding is invalid")
@@ -387,6 +506,9 @@ def validate_receipt(
387
506
 
388
507
 
389
508
  def monitor_descendant_regression() -> None:
509
+ if os.name == "nt":
510
+ print("SKIP POSIX shell process-group fixture; test-windows-portability.py exercises native Node/Python trees")
511
+ return
390
512
  with tempfile.TemporaryDirectory() as raw_tmp:
391
513
  work = pathlib.Path(raw_tmp)
392
514
  fixture_bin = work / "bin"
@@ -428,7 +550,7 @@ def monitor_descendant_regression() -> None:
428
550
  key.startswith("DEVLYN_INVOCATION_")
429
551
  or key.startswith("DEVLYN_WATCHDOG_TEST_")
430
552
  or key.startswith("CODEX_MONITORED_")
431
- or key in {"CODEX_BLOCKED", "CODEX_REAL_BIN"}
553
+ or key in {"CODEX_BLOCKED", "CODEX_REAL_BIN", "DEVLYN_CODEX_PROMPT_FILE"}
432
554
  ):
433
555
  env.pop(key)
434
556
  env.update({
@@ -879,6 +1001,9 @@ def self_test() -> int:
879
1001
  model="gpt-wrapper", prompt_sha256=sha256(glued_prompt.read_bytes()),
880
1002
  session_path=glued_session,
881
1003
  )["sandbox_network_access"] is True
1004
+ if os.name == "nt":
1005
+ print("SKIP POSIX executable-shell wrapper fixtures; native npm worker/judge transport is covered by test-windows-portability.py")
1006
+ return 0
882
1007
  fake_codex = work / "fake-codex"
883
1008
  fake_codex.write_text(
884
1009
  "#!/usr/bin/env bash\n"
@@ -903,6 +1028,7 @@ def self_test() -> int:
903
1028
  assert b"forbidden Codex sandbox" in widened.stderr
904
1029
  assert widened.stdout == b""
905
1030
  env = os.environ.copy()
1031
+ env.pop("DEVLYN_CODEX_PROMPT_FILE", None)
906
1032
  env.update({
907
1033
  "CODEX_BIN": str(fake_codex),
908
1034
  "CODEX_MONITORED_HEARTBEAT": "1",
@@ -952,6 +1078,7 @@ def self_test() -> int:
952
1078
  wrapped_plan_session = devlyn / "plan.worker-session.1.jsonl"
953
1079
  wrapped_plan_receipt = devlyn / "plan.invocation.1.json"
954
1080
  plan_env = os.environ.copy()
1081
+ plan_env.pop("DEVLYN_CODEX_PROMPT_FILE", None)
955
1082
  plan_env.update({
956
1083
  "CODEX_BIN": str(fake_codex),
957
1084
  "CODEX_MONITORED_HEARTBEAT": "1",
@@ -995,6 +1122,56 @@ def self_test() -> int:
995
1122
  return 0
996
1123
 
997
1124
 
1125
+ def complete_dispatch(exit_code):
1126
+ prompt = os.environ.get("DEVLYN_CODEX_PROMPT_FILE")
1127
+ if prompt:
1128
+ path = pathlib.Path(prompt).resolve()
1129
+ finish_transport(path.with_name(path.name + ".transport.json"), exit_code)
1130
+ if os.environ.get("DEVLYN_INVOCATION_RECEIPT"):
1131
+ work = pathlib.Path(os.environ["DEVLYN_INVOCATION_WORKDIR"]).resolve()
1132
+ path = pathlib.Path(os.environ["DEVLYN_INVOCATION_RECEIPT"])
1133
+ finish_receipt(work, path if path.is_absolute() else work / path, exit_code)
1134
+
1135
+
1136
+ def dispatch_codex(args):
1137
+ argv = args.argv[1:] if args.argv[:1] == ["--"] else args.argv
1138
+ command = [args.binary, "exec", *argv]
1139
+ actual = PLATFORM["native_argv"](command)
1140
+ prompt = os.environ.get("DEVLYN_CODEX_PROMPT_FILE")
1141
+ if prompt == "":
1142
+ raise ReceiptError("DEVLYN_CODEX_PROMPT_FILE must name a readable prompt file")
1143
+ stream, carrier, transport = None, None, None
1144
+ try:
1145
+ if prompt:
1146
+ file_prompt_args(argv)
1147
+ stream, carrier, transport = prepare_transport(
1148
+ prompt, command, actual, args.timeout,
1149
+ isolated=bool(os.environ.get("CODEX_MONITORED_ISOLATED")),
1150
+ )
1151
+ if os.environ.get("DEVLYN_INVOCATION_RECEIPT"):
1152
+ env = os.environ
1153
+ work = pathlib.Path(env["DEVLYN_INVOCATION_WORKDIR"]).resolve()
1154
+ path = pathlib.Path(env["DEVLYN_INVOCATION_RECEIPT"])
1155
+ start_receipt(work, path if path.is_absolute() else work / path,
1156
+ env["DEVLYN_INVOCATION_RUN_ID"], env["DEVLYN_INVOCATION_PHASE"],
1157
+ int(env["DEVLYN_INVOCATION_ROUND"]), env["DEVLYN_INVOCATION_PROMPT_FILE"],
1158
+ env["DEVLYN_INVOCATION_SESSION_FILE"], argv, transport=transport)
1159
+ if transport is not None:
1160
+ write_transport(carrier, transport)
1161
+ if os.name != "nt":
1162
+ if stream is not None:
1163
+ os.dup2(stream.fileno(), 0)
1164
+ os.execvp(actual[0], actual)
1165
+ code = PLATFORM["run_process"](actual, stream if stream is not None else subprocess.DEVNULL,
1166
+ args.timeout, heartbeat=args.heartbeat)
1167
+ complete_dispatch(code)
1168
+ print(f"[codex-monitored] codex exited: code={code}", file=sys.stderr)
1169
+ return code
1170
+ finally:
1171
+ if stream is not None:
1172
+ stream.close()
1173
+
1174
+
998
1175
  def main() -> int:
999
1176
  parser = argparse.ArgumentParser(description=__doc__)
1000
1177
  parser.add_argument("--self-test", action="store_true")
@@ -1012,11 +1189,22 @@ def main() -> int:
1012
1189
  finish.add_argument("--workdir", required=True)
1013
1190
  finish.add_argument("--receipt", required=True)
1014
1191
  finish.add_argument("--exit-code", type=int, required=True)
1192
+ dispatch = subparsers.add_parser("dispatch")
1193
+ dispatch.add_argument("--binary", required=True)
1194
+ dispatch.add_argument("--timeout", type=int, required=True)
1195
+ dispatch.add_argument("--heartbeat", type=int, required=True)
1196
+ dispatch.add_argument("argv", nargs=argparse.REMAINDER)
1197
+ completed = subparsers.add_parser("complete-dispatch")
1198
+ completed.add_argument("--exit-code", type=int, required=True)
1015
1199
  args = parser.parse_args()
1016
1200
  if args.self_test:
1017
1201
  return self_test()
1018
1202
  try:
1019
- if args.action == "start":
1203
+ if args.action == "dispatch":
1204
+ return dispatch_codex(args)
1205
+ elif args.action == "complete-dispatch":
1206
+ complete_dispatch(args.exit_code)
1207
+ elif args.action == "start":
1020
1208
  argv = args.argv[1:] if args.argv[:1] == ["--"] else args.argv
1021
1209
  start_receipt(
1022
1210
  pathlib.Path(args.workdir).resolve(), pathlib.Path(args.receipt), args.run_id,
@@ -1038,4 +1226,5 @@ def main() -> int:
1038
1226
 
1039
1227
 
1040
1228
  if __name__ == "__main__":
1041
- raise SystemExit(main())
1229
+ runpy.run_path(str(pathlib.Path(__file__).with_name("platform-support.py")))["configure_utf8"]()
1230
+ raise SystemExit(PLATFORM["system_exit_code"](main()))
@@ -88,7 +88,29 @@ def describe(devlyn, state, role, exit_code):
88
88
  else:
89
89
  stderr = raw.decode()
90
90
  require(isinstance(argv, list) and all(isinstance(x, str) for x in argv), "argv must be an array of strings")
91
- require(prompt in argv, "canonical prompt is not an exact dispatched argument")
91
+ file_mode = ((devlyn / (stem + ".prompt.transport.json")).exists()
92
+ or ("--stdin-file" in argv if engine == "claude" else "-" in argv))
93
+ transport = None
94
+ if file_mode:
95
+ artifacts["transport"], _ = seal(devlyn, stem + ".prompt.transport.json")
96
+ transport = runpy.run_path(Path(__file__).with_name("invocation-receipt.py"))["validate_transport"](
97
+ devlyn / (stem + ".prompt.transport.json"), prompt.encode("utf-8"))
98
+ require(transport["exit_code"] == exit_code and transport["timeout_sec"] == 600, "file transport exit/budget mismatch")
99
+ if engine == "claude":
100
+ index = next((i for i, arg in enumerate(argv) if Path(arg).name == "run-bounded.py"), -1)
101
+ require(index >= 0 and argv[index + 1:index + 3] == ["600", "--stdin-file"]
102
+ and len(argv) > index + 6 and argv[index + 4:index + 6] == ["--record-transport", "--"], "invalid bounded file transport")
103
+ require(Path(argv[index + 3]).resolve() == devlyn / (stem + ".prompt"), "bounded prompt path mismatch")
104
+ require(transport["command"] == argv[index + 6:] and prompt not in transport["command"], "bounded actual argv mismatch")
105
+ require("-p" in transport["command"] or "--print" in transport["command"], "Claude print mode missing")
106
+ else:
107
+ index = next((i for i, arg in enumerate(argv) if Path(arg).name == "codex-monitored.sh"), -1)
108
+ require(index >= 0, "monitored file transport missing")
109
+ isolation = ["--ignore-user-config", "--ignore-rules", "--ephemeral", "--disable", "codex_hooks", "--disable", "hooks"]
110
+ require(transport["isolated"] and transport["command"][1:] == ["exec", *isolation, *argv[index + 1:]], "actual isolated argv mismatch")
111
+ runpy.run_path(Path(__file__).with_name("invocation-receipt.py"))["file_prompt_args"](transport["command"][2:])
112
+ else:
113
+ require(prompt in argv, "canonical prompt is not an exact dispatched argument")
92
114
  diagnostics = stderr.partition("\nuser\n")[0] if engine == "codex" else stderr
93
115
  require(not re.search(r"(?im)^.*(?:model|effort).*\b(?:ignor\w*|clamp\w*|unsupported|not supported)\b", diagnostics), "native diagnostic rejected or ignored an explicit option")
94
116
  requested_model, requested_effort = entry.get("model_requested"), entry.get("effort_requested")
@@ -100,7 +122,7 @@ def describe(devlyn, state, role, exit_code):
100
122
  ("--allowedTools", "Read,Grep,Glob"), ("--setting-sources", "project"), ("--output-format", "json")):
101
123
  require(option(argv, flag) == expected, f"Claude {flag} differs from read-only contract")
102
124
  require("--strict-mcp-config" in argv and loads(option(argv, "--mcp-config") or "null") == {"mcpServers": {}}, "Claude MCP isolation missing")
103
- require(any(Path(arg).name == "run-bounded.py" and argv[i + 1:i + 3] == ["600", "--"] for i, arg in enumerate(argv)), "Claude 600s bound missing")
125
+ require(transport is not None or any(Path(arg).name == "run-bounded.py" and argv[i + 1:i + 3] == ["600", "--"] for i, arg in enumerate(argv)), "Claude 600s bound missing")
104
126
  if requested_effort:
105
127
  require(option(argv, "--effort") == requested_effort, "requested effort differs from argv")
106
128
  artifacts["raw"], raw = seal(devlyn, stem + ".output.json")
@@ -208,14 +230,18 @@ def self_test():
208
230
  receipt, _ = describe(devlyn, state, "pair_judge", 0)
209
231
  assert receipt["model_observed"] == "gpt-6-astra"
210
232
  alias = work / "logical-cwd"
211
- alias.symlink_to(work, target_is_directory=True)
233
+ if sys.platform == "win32":
234
+ import subprocess
235
+ subprocess.run(["cmd.exe", "/d", "/c", "mklink", "/J", str(alias), str(work)], check=True, capture_output=True)
236
+ else:
237
+ alias.symlink_to(work, target_is_directory=True)
212
238
  alias_argv = [str(alias) if arg == str(work) else arg for arg in argv]
213
239
  alias_header = header.replace(f"workdir: {work}\n", f"workdir: {alias}\n")
214
240
  (devlyn / "codex-judge.r0.argv.json").write_bytes(encoded(alias_argv))
215
- (devlyn / "codex-judge.r0.stderr").write_text(alias_header)
241
+ (devlyn / "codex-judge.r0.stderr").write_text(alias_header, encoding="utf-8")
216
242
  receipt, _ = describe(devlyn, state, "pair_judge", 0)
217
243
  assert receipt["artifacts"]["argv"]["sha256"] == digest(encoded(alias_argv))
218
- assert (devlyn / "codex-judge.r0.stderr").read_text() == alias_header
244
+ assert (devlyn / "codex-judge.r0.stderr").read_text(encoding="utf-8") == alias_header
219
245
  elsewhere = work / "elsewhere"; elsewhere.mkdir()
220
246
  (devlyn / "codex-judge.r0.argv.json").write_bytes(encoded([str(elsewhere) if arg == str(work) else arg for arg in argv]))
221
247
  try:
@@ -226,7 +252,7 @@ def self_test():
226
252
  raise AssertionError("different physical worktree accepted")
227
253
  (devlyn / "codex-judge.r0.argv.json").write_bytes(encoded(argv))
228
254
  for bad in ("Warning: model effort unsupported and ignored\n" + header, "\nuser\n" + header, header.replace("\nmodel:", "\nmodel: gpt-5.6-sol\nmodel:"), header.replace("sandbox: read-only", "sandbox: workspace-write"), header.replace("model: gpt-6-astra", "model: gpt-5.6-sol"), header.replace("[codex-monitored] isolated=1\n", ""), header.replace("timeout=600s", "timeout=300s")):
229
- (devlyn / "codex-judge.r0.stderr").write_text(bad)
255
+ (devlyn / "codex-judge.r0.stderr").write_text(bad, encoding="utf-8")
230
256
  try:
231
257
  describe(devlyn, state, "pair_judge", 0)
232
258
  except ValueError:
@@ -270,4 +296,5 @@ def main():
270
296
 
271
297
 
272
298
  if __name__ == "__main__":
299
+ runpy.run_path(str(Path(__file__).with_name("platform-support.py")))["configure_utf8"]()
273
300
  raise SystemExit(main())
@@ -2,6 +2,7 @@
2
2
  """Render exact phase-prompt bytes and print their SHA-256 digest."""
3
3
  from __future__ import annotations
4
4
 
5
+ import runpy
5
6
  import argparse
6
7
  import hashlib
7
8
  import os
@@ -178,4 +179,5 @@ def main() -> int:
178
179
 
179
180
 
180
181
  if __name__ == "__main__":
182
+ runpy.run_path(str(pathlib.Path(__file__).with_name("platform-support.py")))["configure_utf8"]()
181
183
  raise SystemExit(main())