devlyn-cli 3.0.1 → 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 (32) hide show
  1. package/AGENTS.md +1 -1
  2. package/CLAUDE.md +3 -3
  3. package/README.md +20 -3
  4. package/bin/devlyn.js +69 -23
  5. package/config/skills/_shared/adapters/claude.md +3 -2
  6. package/config/skills/_shared/adapters/codex.md +1 -1
  7. package/config/skills/_shared/archive_run.py +5 -3
  8. package/config/skills/_shared/codex-config.md +13 -8
  9. package/config/skills/_shared/codex-monitored.sh +12 -19
  10. package/config/skills/_shared/collect-codex-findings.py +1 -0
  11. package/config/skills/_shared/engine-preflight.md +2 -0
  12. package/config/skills/_shared/finish-gate.py +4 -1
  13. package/config/skills/_shared/invocation-receipt.py +194 -5
  14. package/config/skills/_shared/judge-role-evidence.py +33 -6
  15. package/config/skills/_shared/phase-prompt-render.py +2 -0
  16. package/config/skills/_shared/platform-support.py +370 -0
  17. package/config/skills/_shared/process-evidence.py +4 -2
  18. package/config/skills/_shared/resolve-bootstrap.py +102 -59
  19. package/config/skills/_shared/resolve-stop-hook.py +3 -1
  20. package/config/skills/_shared/role-config.py +7 -5
  21. package/config/skills/_shared/run-bounded.py +35 -21
  22. package/config/skills/_shared/spec-verify-check.py +274 -157
  23. package/config/skills/_shared/state-phase-write.py +26 -9
  24. package/config/skills/_shared/task-complete.py +1173 -0
  25. package/config/skills/_shared/verify-merge-findings.py +9 -8
  26. package/config/skills/devlyn:queue/SKILL.md +3 -3
  27. package/config/skills/devlyn:resolve/SKILL.md +14 -7
  28. package/config/skills/devlyn:resolve/references/outer-loop.md +10 -2
  29. package/config/skills/devlyn:resolve/references/phases/verify.md +5 -3
  30. package/config/skills/devlyn:resolve/references/task-completion.md +117 -0
  31. package/package.json +1 -1
  32. package/scripts/lint-skills.sh +10 -6
@@ -89,6 +89,7 @@ Exit codes:
89
89
 
90
90
  from __future__ import annotations
91
91
 
92
+ import runpy
92
93
  import importlib.util
93
94
  import json
94
95
  import hashlib
@@ -1012,7 +1013,7 @@ def resolve_required_risk_probe_requirements(
1012
1013
  return ([], err)
1013
1014
  reqs = (data or {}).get("required_risk_probe_requirements", [])
1014
1015
  else:
1015
- _section_found, block = extract_verification_block(source_md.read_text())
1016
+ _section_found, block = extract_verification_block(source_md.read_text(encoding="utf-8"))
1016
1017
  if block is None:
1017
1018
  return ([], None)
1018
1019
  try:
@@ -1022,7 +1023,7 @@ def resolve_required_risk_probe_requirements(
1022
1023
  reqs = parsed.get("required_risk_probe_requirements", []) if isinstance(parsed, dict) else []
1023
1024
  if not isinstance(reqs, list):
1024
1025
  return ([], "required_risk_probe_requirements must be a list")
1025
- verification_text = extract_verification_text(source_md.read_text())
1026
+ verification_text = extract_verification_text(source_md.read_text(encoding="utf-8"))
1026
1027
  for i, req in enumerate(reqs):
1027
1028
  err = validate_required_risk_probe_requirement(req, i, verification_text)
1028
1029
  if err:
@@ -1044,12 +1045,12 @@ def load_risk_probes(
1044
1045
  if source_md is None or not source_md.is_file():
1045
1046
  return ([], "risk-probes.jsonl exists but source markdown is unavailable")
1046
1047
 
1047
- verification_text = extract_verification_text(source_md.read_text())
1048
+ verification_text = extract_verification_text(source_md.read_text(encoding="utf-8"))
1048
1049
  if not verification_text:
1049
1050
  return ([], "risk-probes.jsonl exists but source has no <!-- devlyn:verification --> section")
1050
1051
 
1051
1052
  probes: list[dict] = []
1052
- for index, line in enumerate(probes_path.read_text().splitlines()):
1053
+ for index, line in enumerate(probes_path.read_text(encoding="utf-8").splitlines()):
1053
1054
  if not line.strip():
1054
1055
  continue
1055
1056
  try:
@@ -1099,7 +1100,7 @@ def read_source(work: Path, devlyn_dir: Path) -> tuple[str | None, Path | None]:
1099
1100
  if not state_path.is_file():
1100
1101
  return (None, None)
1101
1102
  try:
1102
- state = loads_strict_json(state_path.read_text())
1103
+ state = loads_strict_json(state_path.read_text(encoding="utf-8"))
1103
1104
  except (ValueError, OSError):
1104
1105
  return (None, None)
1105
1106
  src = state.get("source") or {}
@@ -1123,7 +1124,7 @@ def read_state(devlyn_dir: Path) -> dict:
1123
1124
  if not state_path.is_file():
1124
1125
  return {}
1125
1126
  try:
1126
- data = loads_strict_json(state_path.read_text())
1127
+ data = loads_strict_json(state_path.read_text(encoding="utf-8"))
1127
1128
  except (ValueError, OSError):
1128
1129
  return {}
1129
1130
  return data if isinstance(data, dict) else {}
@@ -1176,7 +1177,7 @@ def source_integrity_error(src_type: str | None, state: dict, source_md: Path |
1176
1177
 
1177
1178
  def load_expected_contract(expected_path: Path) -> tuple[dict | None, str | None]:
1178
1179
  try:
1179
- data = loads_strict_json(expected_path.read_text())
1180
+ data = loads_strict_json(expected_path.read_text(encoding="utf-8"))
1180
1181
  except ValueError as e:
1181
1182
  return (None, f"{expected_path} has invalid JSON: {e}")
1182
1183
  except OSError as e:
@@ -1198,7 +1199,7 @@ def stage_from_source(md: Path, devlyn_dir: Path) -> tuple[bool, str | None]:
1198
1199
  error=None → the sentinel is absent entirely (handwritten spec or
1199
1200
  generated source missing the contract).
1200
1201
  """
1201
- section_found, block = extract_verification_block(md.read_text())
1202
+ section_found, block = extract_verification_block(md.read_text(encoding="utf-8"))
1202
1203
  if not section_found:
1203
1204
  return (False, None)
1204
1205
  if block is None:
@@ -1212,7 +1213,7 @@ def stage_from_source(md: Path, devlyn_dir: Path) -> tuple[bool, str | None]:
1212
1213
  return (False, f"`<!-- devlyn:verification -->` ```json``` block in {md}: {err}")
1213
1214
  normalized = {"verification_commands": data["verification_commands"]}
1214
1215
  devlyn_dir.mkdir(parents=True, exist_ok=True)
1215
- (devlyn_dir / "spec-verify.json").write_text(json.dumps(normalized, indent=2) + "\n")
1216
+ (devlyn_dir / "spec-verify.json").write_text(json.dumps(normalized, indent=2) + "\n", encoding="utf-8")
1216
1217
  return (True, None)
1217
1218
 
1218
1219
 
@@ -1243,7 +1244,7 @@ def stage_from_expected(
1243
1244
  return (True, False, None, expected_path, data)
1244
1245
  normalized = {"verification_commands": commands}
1245
1246
  devlyn_dir.mkdir(parents=True, exist_ok=True)
1246
- (devlyn_dir / "spec-verify.json").write_text(json.dumps(normalized, indent=2) + "\n")
1247
+ (devlyn_dir / "spec-verify.json").write_text(json.dumps(normalized, indent=2) + "\n", encoding="utf-8")
1247
1248
  return (True, True, None, expected_path, data)
1248
1249
 
1249
1250
 
@@ -1278,7 +1279,7 @@ def write_malformed_finding(
1278
1279
  "blocking": True,
1279
1280
  "status": "open",
1280
1281
  }
1281
- with findings_path.open("w") as fh:
1282
+ with findings_path.open("w", encoding="utf-8") as fh:
1282
1283
  fh.write(json.dumps(finding) + "\n")
1283
1284
 
1284
1285
 
@@ -1300,7 +1301,7 @@ def write_risk_probe_integrity_finding(devlyn_dir: Path, error: str) -> None:
1300
1301
  "blocking": True,
1301
1302
  "status": "open",
1302
1303
  }
1303
- with findings_path.open("w") as fh:
1304
+ with findings_path.open("w", encoding="utf-8") as fh:
1304
1305
  fh.write(json.dumps(finding) + "\n")
1305
1306
 
1306
1307
 
@@ -1321,14 +1322,14 @@ def diff_text_for_expected(work: Path, devlyn_dir: Path, state: dict) -> tuple[s
1321
1322
  external_diff = devlyn_dir / "external-diff.patch"
1322
1323
  if external_diff.is_file():
1323
1324
  try:
1324
- return (external_diff.read_text(), None)
1325
+ return (external_diff.read_text(encoding="utf-8"), None)
1325
1326
  except OSError as e:
1326
1327
  return ("", f"cannot read {external_diff}: {e}")
1327
1328
  base_sha = ((state.get("base_ref") or {}).get("sha") or "").strip()
1328
1329
  cmd = ["git", "diff"]
1329
1330
  if base_sha:
1330
1331
  cmd.append(base_sha)
1331
- proc = subprocess.run(cmd, cwd=str(work), capture_output=True, text=True)
1332
+ proc = subprocess.run(cmd, cwd=str(work), capture_output=True, text=True, encoding="utf-8")
1332
1333
  if proc.returncode != 0:
1333
1334
  return ("", (proc.stderr or proc.stdout or "git diff failed").strip())
1334
1335
  return (proc.stdout or "", None)
@@ -1340,7 +1341,7 @@ def count_deps_added(work: Path, state: dict) -> int:
1340
1341
  if base_sha:
1341
1342
  cmd.append(base_sha)
1342
1343
  cmd.extend(["--", "package.json"])
1343
- proc = subprocess.run(cmd, cwd=str(work), capture_output=True, text=True)
1344
+ proc = subprocess.run(cmd, cwd=str(work), capture_output=True, text=True, encoding="utf-8")
1344
1345
  if proc.returncode != 0:
1345
1346
  return 0
1346
1347
  in_deps = False
@@ -1365,7 +1366,7 @@ def changed_files(work: Path, state: dict, devlyn_dir: Path) -> list[str]:
1365
1366
  if external_diff.is_file():
1366
1367
  names: list[str] = []
1367
1368
  try:
1368
- external_text = external_diff.read_text()
1369
+ external_text = external_diff.read_text(encoding="utf-8")
1369
1370
  except OSError:
1370
1371
  return []
1371
1372
  for line in external_text.splitlines():
@@ -1378,7 +1379,7 @@ def changed_files(work: Path, state: dict, devlyn_dir: Path) -> list[str]:
1378
1379
  cmd = ["git", "diff", "--name-only"]
1379
1380
  if base_sha:
1380
1381
  cmd.append(base_sha)
1381
- proc = subprocess.run(cmd, cwd=str(work), capture_output=True, text=True)
1382
+ proc = subprocess.run(cmd, cwd=str(work), capture_output=True, text=True, encoding="utf-8")
1382
1383
  if proc.returncode != 0:
1383
1384
  return []
1384
1385
  return [line.strip() for line in (proc.stdout or "").splitlines() if line.strip()]
@@ -1856,7 +1857,7 @@ def run_check_mode(md_path: Path) -> int:
1856
1857
  if not md_path.is_file():
1857
1858
  print(f"[spec-verify --check] error: {md_path} not found", file=sys.stderr)
1858
1859
  return 2
1859
- text = md_path.read_text()
1860
+ text = md_path.read_text(encoding="utf-8")
1860
1861
  frontmatter_err = validate_present_spec_complexity(text)
1861
1862
  if frontmatter_err:
1862
1863
  print(f"[spec-verify --check] {md_path}: {frontmatter_err}", file=sys.stderr)
@@ -1947,6 +1948,7 @@ def run_self_test() -> int:
1947
1948
  cwd=help_tmp,
1948
1949
  capture_output=True,
1949
1950
  text=True,
1951
+ encoding="utf-8",
1950
1952
  )
1951
1953
  if help_result.returncode != 0 or "usage:" not in help_result.stdout:
1952
1954
  print(f"{help_flag} did not return usage successfully", file=sys.stderr)
@@ -1959,6 +1961,7 @@ def run_self_test() -> int:
1959
1961
  cwd=help_tmp,
1960
1962
  capture_output=True,
1961
1963
  text=True,
1964
+ encoding="utf-8",
1962
1965
  )
1963
1966
  if unknown_result.returncode != 2 or "unknown argument" not in unknown_result.stderr:
1964
1967
  print("unknown option did not fail closed", file=sys.stderr)
@@ -1988,15 +1991,15 @@ def run_self_test() -> int:
1988
1991
  else:
1989
1992
  raise AssertionError(f"malformed brace glob accepted: {entry}")
1990
1993
  spec_md = work / "spec.md"
1991
- spec_md.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- probe must pass visible marker.\n")
1994
+ spec_md.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- probe must pass visible marker.\n", encoding="utf-8")
1992
1995
  (devlyn / "pipeline.state.json").write_text(json.dumps({
1993
1996
  "source": {"type": "spec", "spec_path": str(spec_md)}
1994
- }))
1997
+ }), encoding="utf-8")
1995
1998
  (devlyn / "spec-verify.json").write_text(json.dumps({
1996
1999
  "verification_commands": [
1997
2000
  {"cmd": "printf ok", "exit_code": 0, "stdout_contains": ["ok"]}
1998
2001
  ]
1999
- }) + "\n")
2002
+ }) + "\n", encoding="utf-8")
2000
2003
  probes_dir = devlyn / "probes"
2001
2004
  probes_dir.mkdir()
2002
2005
  probe_script = probes_dir / "P1.py"
@@ -2018,7 +2021,7 @@ def run_self_test() -> int:
2018
2021
  ],
2019
2022
  },
2020
2023
  }
2021
- (devlyn / "risk-probes.jsonl").write_text(json.dumps(risk_probe_payload) + "\n")
2024
+ (devlyn / "risk-probes.jsonl").write_text(json.dumps(risk_probe_payload) + "\n", encoding="utf-8")
2022
2025
  loaded_probes, loaded_probe_error = load_risk_probes(
2023
2026
  devlyn, spec_md, require_present=True
2024
2027
  )
@@ -2068,7 +2071,7 @@ def run_self_test() -> int:
2068
2071
  inline_timeout_spec, inline_timeout_devlyn
2069
2072
  )
2070
2073
  inline_staged = loads_strict_json(
2071
- (inline_timeout_devlyn / "spec-verify.json").read_text()
2074
+ (inline_timeout_devlyn / "spec-verify.json").read_text(encoding="utf-8")
2072
2075
  )
2073
2076
  if (
2074
2077
  not staged
@@ -2089,7 +2092,7 @@ def run_self_test() -> int:
2089
2092
  "verification_commands": [
2090
2093
  {"cmd": "printf ok", "timeout_sec": invalid_timeout}
2091
2094
  ]
2092
- }) + "\n")
2095
+ }) + "\n", encoding="utf-8")
2093
2096
  found, staged, sibling_error, _path, _data = stage_from_expected(
2094
2097
  sibling_timeout_spec, sibling_timeout_devlyn
2095
2098
  )
@@ -2104,12 +2107,12 @@ def run_self_test() -> int:
2104
2107
  "verification_commands": [
2105
2108
  {"cmd": "printf ok", "timeout_sec": 5}
2106
2109
  ]
2107
- }) + "\n")
2110
+ }) + "\n", encoding="utf-8")
2108
2111
  found, staged, sibling_error, _path, _data = stage_from_expected(
2109
2112
  sibling_timeout_spec, sibling_timeout_devlyn
2110
2113
  )
2111
2114
  sibling_staged = loads_strict_json(
2112
- (sibling_timeout_devlyn / "spec-verify.json").read_text()
2115
+ (sibling_timeout_devlyn / "spec-verify.json").read_text(encoding="utf-8")
2113
2116
  )
2114
2117
  if (
2115
2118
  not found
@@ -2149,23 +2152,24 @@ def run_self_test() -> int:
2149
2152
  "run_id": "rs-timeout-run",
2150
2153
  "source": {"type": "spec", "spec_path": str(timeout_run_spec)},
2151
2154
  "phases": {"build_gate": {"round": 2}},
2152
- }))
2155
+ }), encoding="utf-8")
2153
2156
  timeout_run = subprocess.run(
2154
2157
  [sys.executable, script_path],
2155
2158
  cwd=timeout_run_root,
2156
2159
  capture_output=True,
2157
2160
  text=True,
2161
+ encoding="utf-8",
2158
2162
  )
2159
2163
  if timeout_run.returncode == 0:
2160
2164
  print("declared one-second timeout did not fail", file=sys.stderr)
2161
2165
  return 1
2162
2166
  timeout_document = loads_strict_json(
2163
- (timeout_run_devlyn / "spec-verify.results.json").read_text()
2167
+ (timeout_run_devlyn / "spec-verify.results.json").read_text(encoding="utf-8")
2164
2168
  )
2165
2169
  timeout_results = timeout_document["commands"]
2166
2170
  timeout_findings = [
2167
2171
  loads_strict_json(line)
2168
- for line in (timeout_run_devlyn / output_findings_name()).read_text().splitlines()
2172
+ for line in (timeout_run_devlyn / output_findings_name()).read_text(encoding="utf-8").splitlines()
2169
2173
  if line.strip()
2170
2174
  ]
2171
2175
  if (
@@ -2185,7 +2189,7 @@ def run_self_test() -> int:
2185
2189
  ".devlyn/process-evidence/rs-timeout-run/build_gate/round-2/manifest.json"
2186
2190
  )
2187
2191
  timeout_manifest = loads_strict_json(
2188
- (timeout_run_root / timeout_manifest_path).read_text()
2192
+ (timeout_run_root / timeout_manifest_path).read_text(encoding="utf-8")
2189
2193
  )
2190
2194
  if (
2191
2195
  timeout_carrier.get("manifest", {}).get("path") != timeout_manifest_path
@@ -2249,7 +2253,7 @@ def run_self_test() -> int:
2249
2253
  "run_id": "rs-runner-env",
2250
2254
  "source": {"type": "spec", "spec_path": str(runner_env_spec)},
2251
2255
  "phases": {"verify": {"round": 3}},
2252
- }))
2256
+ }), encoding="utf-8")
2253
2257
  runner_env = os.environ.copy()
2254
2258
  runner_env.update({
2255
2259
  "SPEC_VERIFY_PHASE": "verify_mechanical",
@@ -2262,9 +2266,10 @@ def run_self_test() -> int:
2262
2266
  env=runner_env,
2263
2267
  capture_output=True,
2264
2268
  text=True,
2269
+ encoding="utf-8",
2265
2270
  )
2266
2271
  runner_env_document = loads_strict_json(
2267
- (runner_env_devlyn / "spec-verify.results.json").read_text()
2272
+ (runner_env_devlyn / "spec-verify.results.json").read_text(encoding="utf-8")
2268
2273
  )
2269
2274
  runner_env_results = runner_env_document["commands"]
2270
2275
  if runner_env_run.returncode != 0 or not runner_env_results[0].get("pass"):
@@ -2285,6 +2290,7 @@ def run_self_test() -> int:
2285
2290
  env=env,
2286
2291
  capture_output=True,
2287
2292
  text=True,
2293
+ encoding="utf-8",
2288
2294
  )
2289
2295
  if validate_without_digest.returncode != 0:
2290
2296
  print("--validate-risk-probes rejected valid probes without digest", file=sys.stderr)
@@ -2296,6 +2302,7 @@ def run_self_test() -> int:
2296
2302
  env=env,
2297
2303
  capture_output=True,
2298
2304
  text=True,
2305
+ encoding="utf-8",
2299
2306
  )
2300
2307
  if digest_run.returncode != 0:
2301
2308
  print("--print-risk-probes-digest rejected valid probes", file=sys.stderr)
@@ -2312,23 +2319,24 @@ def run_self_test() -> int:
2312
2319
  "risk_profile": {"risk_probes_enabled": True},
2313
2320
  "risk_probes_digest": risk_digest,
2314
2321
  "phases": {"build_gate": {"round": 4}},
2315
- }))
2322
+ }), encoding="utf-8")
2316
2323
  good = subprocess.run(
2317
2324
  [sys.executable, script_path, "--include-risk-probes"],
2318
2325
  cwd=work,
2319
2326
  env=env,
2320
2327
  capture_output=True,
2321
2328
  text=True,
2329
+ encoding="utf-8",
2322
2330
  )
2323
2331
  if good.returncode != 0:
2324
2332
  print(good.stderr, file=sys.stderr)
2325
2333
  return 1
2326
2334
  good_document = loads_strict_json(
2327
- (devlyn / "spec-verify.results.json").read_text()
2335
+ (devlyn / "spec-verify.results.json").read_text(encoding="utf-8")
2328
2336
  )
2329
2337
  good_manifest_path = good_document.get("process_evidence", {}).get("manifest", {}).get("path")
2330
2338
  good_manifest = (
2331
- loads_strict_json((work / good_manifest_path).read_text())
2339
+ loads_strict_json((work / good_manifest_path).read_text(encoding="utf-8"))
2332
2340
  if isinstance(good_manifest_path, str)
2333
2341
  else {}
2334
2342
  )
@@ -2349,6 +2357,7 @@ def run_self_test() -> int:
2349
2357
  env=env,
2350
2358
  capture_output=True,
2351
2359
  text=True,
2360
+ encoding="utf-8",
2352
2361
  )
2353
2362
  if mutated_script.returncode == 0:
2354
2363
  print("--include-risk-probes accepted mutated probe script bytes", file=sys.stderr)
@@ -2362,13 +2371,14 @@ def run_self_test() -> int:
2362
2371
 
2363
2372
  mutated_payload = dict(risk_probe_payload)
2364
2373
  mutated_payload["id"] = "P1-mutated"
2365
- (devlyn / "risk-probes.jsonl").write_text(json.dumps(mutated_payload) + "\n")
2374
+ (devlyn / "risk-probes.jsonl").write_text(json.dumps(mutated_payload) + "\n", encoding="utf-8")
2366
2375
  mutated_jsonl = subprocess.run(
2367
2376
  [sys.executable, script_path, "--include-risk-probes"],
2368
2377
  cwd=work,
2369
2378
  env=env,
2370
2379
  capture_output=True,
2371
2380
  text=True,
2381
+ encoding="utf-8",
2372
2382
  )
2373
2383
  if mutated_jsonl.returncode == 0:
2374
2384
  print("--include-risk-probes accepted mutated risk-probes.jsonl bytes", file=sys.stderr)
@@ -2378,18 +2388,19 @@ def run_self_test() -> int:
2378
2388
  print("mutated risk-probes.jsonl did not emit correctness.risk-probe-integrity", file=sys.stderr)
2379
2389
  print(integrity_findings, file=sys.stderr)
2380
2390
  return 1
2381
- (devlyn / "risk-probes.jsonl").write_text(json.dumps(risk_probe_payload) + "\n")
2391
+ (devlyn / "risk-probes.jsonl").write_text(json.dumps(risk_probe_payload) + "\n", encoding="utf-8")
2382
2392
 
2383
2393
  (devlyn / "pipeline.state.json").write_text(json.dumps({
2384
2394
  "source": {"type": "spec", "spec_path": str(spec_md)},
2385
2395
  "risk_profile": {"risk_probes_enabled": True},
2386
- }))
2396
+ }), encoding="utf-8")
2387
2397
  missing_digest = subprocess.run(
2388
2398
  [sys.executable, script_path, "--include-risk-probes"],
2389
2399
  cwd=work,
2390
2400
  env=env,
2391
2401
  capture_output=True,
2392
2402
  text=True,
2403
+ encoding="utf-8",
2393
2404
  )
2394
2405
  if missing_digest.returncode == 0:
2395
2406
  print("--include-risk-probes accepted enabled risk probes with missing digest", file=sys.stderr)
@@ -2407,6 +2418,7 @@ def run_self_test() -> int:
2407
2418
  env=env,
2408
2419
  capture_output=True,
2409
2420
  text=True,
2421
+ encoding="utf-8",
2410
2422
  )
2411
2423
  if missing_jsonl_digest.returncode != 2:
2412
2424
  print("--print-risk-probes-digest accepted missing risk-probes.jsonl", file=sys.stderr)
@@ -2420,13 +2432,14 @@ def run_self_test() -> int:
2420
2432
  "source": {"type": "spec", "spec_path": str(spec_md)},
2421
2433
  "risk_profile": {"risk_probes_enabled": True},
2422
2434
  "risk_probes_digest": risk_digest,
2423
- }))
2435
+ }), encoding="utf-8")
2424
2436
  missing_required_probe = subprocess.run(
2425
2437
  [sys.executable, script_path, "--include-risk-probes"],
2426
2438
  cwd=work,
2427
2439
  env=env,
2428
2440
  capture_output=True,
2429
2441
  text=True,
2442
+ encoding="utf-8",
2430
2443
  )
2431
2444
  if missing_required_probe.returncode == 0:
2432
2445
  print("--include-risk-probes accepted missing required risk-probes.jsonl", file=sys.stderr)
@@ -2439,13 +2452,14 @@ def run_self_test() -> int:
2439
2452
  (devlyn / "pipeline.state.json").write_text(json.dumps({
2440
2453
  "source": {"type": "spec", "spec_path": str(spec_md)},
2441
2454
  "risk_profile": {"risk_probes_enabled": False},
2442
- }))
2455
+ }), encoding="utf-8")
2443
2456
  missing_optional_probe = subprocess.run(
2444
2457
  [sys.executable, script_path, "--include-risk-probes"],
2445
2458
  cwd=work,
2446
2459
  env=env,
2447
2460
  capture_output=True,
2448
2461
  text=True,
2462
+ encoding="utf-8",
2449
2463
  )
2450
2464
  if missing_optional_probe.returncode != 0:
2451
2465
  print("--include-risk-probes rejected optional missing risk-probes.jsonl", file=sys.stderr)
@@ -2455,13 +2469,14 @@ def run_self_test() -> int:
2455
2469
  (devlyn / "pipeline.state.json").write_text(json.dumps({
2456
2470
  "source": {"type": "spec", "spec_path": str(spec_md)},
2457
2471
  "risk_profile": {"risk_probes_enabled": "true"},
2458
- }))
2472
+ }), encoding="utf-8")
2459
2473
  malformed_risk_probe_state = subprocess.run(
2460
2474
  [sys.executable, script_path, "--include-risk-probes"],
2461
2475
  cwd=work,
2462
2476
  env=env,
2463
2477
  capture_output=True,
2464
2478
  text=True,
2479
+ encoding="utf-8",
2465
2480
  )
2466
2481
  if malformed_risk_probe_state.returncode == 0:
2467
2482
  print("--include-risk-probes accepted non-boolean risk_probes_enabled", file=sys.stderr)
@@ -2474,13 +2489,14 @@ def run_self_test() -> int:
2474
2489
  (devlyn / "pipeline.state.json").write_text(json.dumps({
2475
2490
  "source": {"type": "spec", "spec_path": str(spec_md)},
2476
2491
  "risk_profile": "enabled",
2477
- }))
2492
+ }), encoding="utf-8")
2478
2493
  malformed_risk_profile = subprocess.run(
2479
2494
  [sys.executable, script_path, "--include-risk-probes"],
2480
2495
  cwd=work,
2481
2496
  env=env,
2482
2497
  capture_output=True,
2483
2498
  text=True,
2499
+ encoding="utf-8",
2484
2500
  )
2485
2501
  if malformed_risk_profile.returncode == 0:
2486
2502
  print("--include-risk-probes accepted non-object risk_profile", file=sys.stderr)
@@ -2492,7 +2508,7 @@ def run_self_test() -> int:
2492
2508
 
2493
2509
  (devlyn / "pipeline.state.json").write_text(json.dumps({
2494
2510
  "source": {"type": "spec", "spec_path": str(spec_md)}
2495
- }))
2511
+ }), encoding="utf-8")
2496
2512
  (devlyn / "risk-probes.jsonl").write_text(json.dumps({
2497
2513
  "id": "P1",
2498
2514
  "derived_from": "probe must pass visible marker.",
@@ -2508,7 +2524,7 @@ def run_self_test() -> int:
2508
2524
  "asserts_no_unexpected_output_keys",
2509
2525
  ],
2510
2526
  },
2511
- }) + "\n")
2527
+ }) + "\n", encoding="utf-8")
2512
2528
 
2513
2529
  good_complexity = work / "good-complexity.md"
2514
2530
  good_complexity.write_text(
@@ -2520,6 +2536,7 @@ def run_self_test() -> int:
2520
2536
  cwd=work,
2521
2537
  capture_output=True,
2522
2538
  text=True,
2539
+ encoding="utf-8",
2523
2540
  )
2524
2541
  if good_complexity_check.returncode != 0:
2525
2542
  print(good_complexity_check.stderr, file=sys.stderr)
@@ -2535,6 +2552,7 @@ def run_self_test() -> int:
2535
2552
  cwd=work,
2536
2553
  capture_output=True,
2537
2554
  text=True,
2555
+ encoding="utf-8",
2538
2556
  )
2539
2557
  if bad_complexity_check.returncode == 0:
2540
2558
  print("unsupported spec complexity was accepted", file=sys.stderr)
@@ -2556,6 +2574,7 @@ def run_self_test() -> int:
2556
2574
  cwd=work,
2557
2575
  capture_output=True,
2558
2576
  text=True,
2577
+ encoding="utf-8",
2559
2578
  )
2560
2579
  if weak_solo_check.returncode == 0:
2561
2580
  print("weak solo-headroom hypothesis was accepted by --check", file=sys.stderr)
@@ -2576,6 +2595,7 @@ def run_self_test() -> int:
2576
2595
  cwd=work,
2577
2596
  capture_output=True,
2578
2597
  text=True,
2598
+ encoding="utf-8",
2579
2599
  )
2580
2600
  if weak_descriptive_check.returncode == 0:
2581
2601
  print("descriptive backtick solo-headroom hypothesis was accepted by --check", file=sys.stderr)
@@ -2592,6 +2612,7 @@ def run_self_test() -> int:
2592
2612
  cwd=work,
2593
2613
  capture_output=True,
2594
2614
  text=True,
2615
+ encoding="utf-8",
2595
2616
  )
2596
2617
  if strong_solo_check.returncode != 0:
2597
2618
  print("actionable solo-headroom hypothesis was rejected by --check", file=sys.stderr)
@@ -2610,6 +2631,7 @@ def run_self_test() -> int:
2610
2631
  cwd=work,
2611
2632
  capture_output=True,
2612
2633
  text=True,
2634
+ encoding="utf-8",
2613
2635
  )
2614
2636
  if docs_style_solo_check.returncode != 0:
2615
2637
  print("docs-style solo-headroom hypothesis was rejected by --check", file=sys.stderr)
@@ -2627,6 +2649,7 @@ def run_self_test() -> int:
2627
2649
  cwd=work,
2628
2650
  capture_output=True,
2629
2651
  text=True,
2652
+ encoding="utf-8",
2630
2653
  )
2631
2654
  if weak_solo_ceiling_check.returncode == 0:
2632
2655
  print("weak solo ceiling avoidance was accepted by --check", file=sys.stderr)
@@ -2648,6 +2671,7 @@ def run_self_test() -> int:
2648
2671
  cwd=work,
2649
2672
  capture_output=True,
2650
2673
  text=True,
2674
+ encoding="utf-8",
2651
2675
  )
2652
2676
  if strong_solo_ceiling_check.returncode != 0:
2653
2677
  print("actionable solo ceiling avoidance was rejected by --check", file=sys.stderr)
@@ -2669,6 +2693,7 @@ def run_self_test() -> int:
2669
2693
  cwd=work,
2670
2694
  capture_output=True,
2671
2695
  text=True,
2696
+ encoding="utf-8",
2672
2697
  )
2673
2698
  if inline_mismatched_check.returncode == 0:
2674
2699
  print("mismatched inline solo-headroom command was accepted by --check", file=sys.stderr)
@@ -2693,6 +2718,7 @@ def run_self_test() -> int:
2693
2718
  cwd=work,
2694
2719
  capture_output=True,
2695
2720
  text=True,
2721
+ encoding="utf-8",
2696
2722
  )
2697
2723
  if inline_matched_check.returncode != 0:
2698
2724
  print("matched inline solo-headroom command was rejected by --check", file=sys.stderr)
@@ -2704,13 +2730,14 @@ def run_self_test() -> int:
2704
2730
  "derived_from": "probe must pass visible marker.",
2705
2731
  "cmd": "node $BENCH_FIXTURE_DIR/verifiers/hidden.js",
2706
2732
  "exit_code": 0,
2707
- }) + "\n")
2733
+ }) + "\n", encoding="utf-8")
2708
2734
  bad = subprocess.run(
2709
2735
  [sys.executable, script_path, "--validate-risk-probes"],
2710
2736
  cwd=work,
2711
2737
  env=env,
2712
2738
  capture_output=True,
2713
2739
  text=True,
2740
+ encoding="utf-8",
2714
2741
  )
2715
2742
  if bad.returncode == 0:
2716
2743
  print("hidden verifier path was accepted", file=sys.stderr)
@@ -2725,13 +2752,14 @@ def run_self_test() -> int:
2725
2752
  "cmd": "python3 .devlyn/probes/Pscript.py",
2726
2753
  "stdout_contains": ["script-ok"],
2727
2754
  }
2728
- (devlyn / "risk-probes.jsonl").write_text(json.dumps(script_probe_payload) + "\n")
2755
+ (devlyn / "risk-probes.jsonl").write_text(json.dumps(script_probe_payload) + "\n", encoding="utf-8")
2729
2756
  good_script_probe = subprocess.run(
2730
2757
  [sys.executable, script_path, "--validate-risk-probes"],
2731
2758
  cwd=work,
2732
2759
  env=env,
2733
2760
  capture_output=True,
2734
2761
  text=True,
2762
+ encoding="utf-8",
2735
2763
  )
2736
2764
  if good_script_probe.returncode != 0:
2737
2765
  print("risk probe script file was rejected", file=sys.stderr)
@@ -2743,13 +2771,14 @@ def run_self_test() -> int:
2743
2771
  "id": "Pmixed",
2744
2772
  "cmd": "python3 .devlyn/probes/Pscript.py && python3 ./.devlyn/probes/P1.py",
2745
2773
  }
2746
- (devlyn / "risk-probes.jsonl").write_text(json.dumps(mixed_script_payload) + "\n")
2774
+ (devlyn / "risk-probes.jsonl").write_text(json.dumps(mixed_script_payload) + "\n", encoding="utf-8")
2747
2775
  mixed_script_probe = subprocess.run(
2748
2776
  [sys.executable, script_path, "--validate-risk-probes"],
2749
2777
  cwd=work,
2750
2778
  env=env,
2751
2779
  capture_output=True,
2752
2780
  text=True,
2781
+ encoding="utf-8",
2753
2782
  )
2754
2783
  if mixed_script_probe.returncode != 0:
2755
2784
  print("canonical and ./-alias probe script references were rejected", file=sys.stderr)
@@ -2780,13 +2809,14 @@ def run_self_test() -> int:
2780
2809
  "derived_from": "probe must pass visible marker.",
2781
2810
  "cmd": "python3 ./.devlyn/probes/missing.py",
2782
2811
  "exit_code": 0,
2783
- }) + "\n")
2812
+ }) + "\n", encoding="utf-8")
2784
2813
  missing_script_probe = subprocess.run(
2785
2814
  [sys.executable, script_path, "--validate-risk-probes"],
2786
2815
  cwd=work,
2787
2816
  env=env,
2788
2817
  capture_output=True,
2789
2818
  text=True,
2819
+ encoding="utf-8",
2790
2820
  )
2791
2821
  if missing_script_probe.returncode == 0:
2792
2822
  print("risk probe missing script file was accepted", file=sys.stderr)
@@ -2801,6 +2831,7 @@ def run_self_test() -> int:
2801
2831
  env=env,
2802
2832
  capture_output=True,
2803
2833
  text=True,
2834
+ encoding="utf-8",
2804
2835
  )
2805
2836
  if missing_script_digest.returncode != 2:
2806
2837
  print("--print-risk-probes-digest accepted a missing referenced script", file=sys.stderr)
@@ -2822,13 +2853,14 @@ def run_self_test() -> int:
2822
2853
  "derived_from": "probe must pass visible marker.",
2823
2854
  "cmd": f"python3 {bad_form}",
2824
2855
  "exit_code": 0,
2825
- }) + "\n")
2856
+ }) + "\n", encoding="utf-8")
2826
2857
  bad_ref_probe = subprocess.run(
2827
2858
  [sys.executable, script_path, "--validate-risk-probes"],
2828
2859
  cwd=work,
2829
2860
  env=env,
2830
2861
  capture_output=True,
2831
2862
  text=True,
2863
+ encoding="utf-8",
2832
2864
  )
2833
2865
  bad_ref_digest = subprocess.run(
2834
2866
  [sys.executable, script_path, "--print-risk-probes-digest"],
@@ -2836,6 +2868,7 @@ def run_self_test() -> int:
2836
2868
  env=env,
2837
2869
  capture_output=True,
2838
2870
  text=True,
2871
+ encoding="utf-8",
2839
2872
  )
2840
2873
  unrecognized = unrecognized_risk_probe_reference(f"python3 {bad_form}")
2841
2874
  expected_error = f"risk-probes[0].cmd has {unrecognized}"
@@ -2856,13 +2889,14 @@ def run_self_test() -> int:
2856
2889
  "derived_from": "probe must pass visible marker.",
2857
2890
  "cmd": "python3 .devlyn/probes/Phidden.py",
2858
2891
  "exit_code": 0,
2859
- }) + "\n")
2892
+ }) + "\n", encoding="utf-8")
2860
2893
  hidden_script_probe = subprocess.run(
2861
2894
  [sys.executable, script_path, "--validate-risk-probes"],
2862
2895
  cwd=work,
2863
2896
  env=env,
2864
2897
  capture_output=True,
2865
2898
  text=True,
2899
+ encoding="utf-8",
2866
2900
  )
2867
2901
  if hidden_script_probe.returncode == 0:
2868
2902
  print("risk probe script containing a hidden fixture path was accepted", file=sys.stderr)
@@ -2878,13 +2912,14 @@ def run_self_test() -> int:
2878
2912
  "derived_from": "probe must pass visible marker.",
2879
2913
  "cmd": "python3 .devlyn/probes/Pexternal.py",
2880
2914
  "exit_code": 0,
2881
- }) + "\n")
2915
+ }) + "\n", encoding="utf-8")
2882
2916
  external_script_probe = subprocess.run(
2883
2917
  [sys.executable, script_path, "--validate-risk-probes"],
2884
2918
  cwd=work,
2885
2919
  env=env,
2886
2920
  capture_output=True,
2887
2921
  text=True,
2922
+ encoding="utf-8",
2888
2923
  )
2889
2924
  if external_script_probe.returncode == 0:
2890
2925
  print("risk probe script containing an external URL was accepted", file=sys.stderr)
@@ -2894,13 +2929,14 @@ def run_self_test() -> int:
2894
2929
  print(external_script_probe.stderr, file=sys.stderr)
2895
2930
  return 1
2896
2931
 
2897
- (devlyn / "risk-probes.jsonl").write_text('{"id":NaN}\n')
2932
+ (devlyn / "risk-probes.jsonl").write_text('{"id":NaN}\n', encoding="utf-8")
2898
2933
  bad_probe_nan = subprocess.run(
2899
2934
  [sys.executable, script_path, "--validate-risk-probes"],
2900
2935
  cwd=work,
2901
2936
  env=env,
2902
2937
  capture_output=True,
2903
2938
  text=True,
2939
+ encoding="utf-8",
2904
2940
  )
2905
2941
  if bad_probe_nan.returncode == 0:
2906
2942
  print("NaN risk-probes JSONL was accepted", file=sys.stderr)
@@ -2917,13 +2953,14 @@ def run_self_test() -> int:
2917
2953
  "exit_code": 0,
2918
2954
  "tags": ["error_contract"],
2919
2955
  "tag_evidence": {"error_contract": []},
2920
- }) + "\n")
2956
+ }) + "\n", encoding="utf-8")
2921
2957
  bad_error_ref = subprocess.run(
2922
2958
  [sys.executable, script_path, "--validate-risk-probes"],
2923
2959
  cwd=work,
2924
2960
  env=env,
2925
2961
  capture_output=True,
2926
2962
  text=True,
2963
+ encoding="utf-8",
2927
2964
  )
2928
2965
  if bad_error_ref.returncode == 0:
2929
2966
  print("error_contract with unrelated derived_from was accepted", file=sys.stderr)
@@ -2951,13 +2988,14 @@ def run_self_test() -> int:
2951
2988
  "asserts_no_unexpected_output_keys",
2952
2989
  ],
2953
2990
  },
2954
- }) + "\n")
2991
+ }) + "\n", encoding="utf-8")
2955
2992
  bad_solo_headroom_probe = subprocess.run(
2956
2993
  [sys.executable, script_path, "--validate-risk-probes"],
2957
2994
  cwd=work,
2958
2995
  env=env,
2959
2996
  capture_output=True,
2960
2997
  text=True,
2998
+ encoding="utf-8",
2961
2999
  )
2962
3000
  if bad_solo_headroom_probe.returncode == 0:
2963
3001
  print("risk probe missing solo-headroom command coverage was accepted", file=sys.stderr)
@@ -2982,13 +3020,14 @@ def run_self_test() -> int:
2982
3020
  "asserts_no_unexpected_output_keys",
2983
3021
  ],
2984
3022
  },
2985
- }) + "\n")
3023
+ }) + "\n", encoding="utf-8")
2986
3024
  bad_solo_headroom_derived_from = subprocess.run(
2987
3025
  [sys.executable, script_path, "--validate-risk-probes"],
2988
3026
  cwd=work,
2989
3027
  env=env,
2990
3028
  capture_output=True,
2991
3029
  text=True,
3030
+ encoding="utf-8",
2992
3031
  )
2993
3032
  if bad_solo_headroom_derived_from.returncode == 0:
2994
3033
  print("risk probe with unrelated solo-headroom derived_from was accepted", file=sys.stderr)
@@ -3029,7 +3068,8 @@ def run_self_test() -> int:
3029
3068
  "asserts_no_unexpected_output_keys",
3030
3069
  ],
3031
3070
  },
3032
- }) + "\n"
3071
+ }) + "\n",
3072
+ encoding="utf-8",
3033
3073
  )
3034
3074
  late_solo_headroom_probe = subprocess.run(
3035
3075
  [sys.executable, script_path, "--validate-risk-probes"],
@@ -3037,6 +3077,7 @@ def run_self_test() -> int:
3037
3077
  env=env,
3038
3078
  capture_output=True,
3039
3079
  text=True,
3080
+ encoding="utf-8",
3040
3081
  )
3041
3082
  if late_solo_headroom_probe.returncode == 0:
3042
3083
  print("solo-headroom command in a later risk probe was accepted", file=sys.stderr)
@@ -3057,13 +3098,14 @@ def run_self_test() -> int:
3057
3098
  "asserts_no_unexpected_output_keys",
3058
3099
  ],
3059
3100
  },
3060
- }) + "\n")
3101
+ }) + "\n", encoding="utf-8")
3061
3102
  prefix_solo_headroom_probe = subprocess.run(
3062
3103
  [sys.executable, script_path, "--validate-risk-probes"],
3063
3104
  cwd=work,
3064
3105
  env=env,
3065
3106
  capture_output=True,
3066
3107
  text=True,
3108
+ encoding="utf-8",
3067
3109
  )
3068
3110
  if prefix_solo_headroom_probe.returncode == 0:
3069
3111
  print("solo-headroom command prefix match was accepted", file=sys.stderr)
@@ -3084,13 +3126,14 @@ def run_self_test() -> int:
3084
3126
  "asserts_no_unexpected_output_keys",
3085
3127
  ],
3086
3128
  },
3087
- }) + "\n")
3129
+ }) + "\n", encoding="utf-8")
3088
3130
  good_solo_headroom_probe = subprocess.run(
3089
3131
  [sys.executable, script_path, "--validate-risk-probes"],
3090
3132
  cwd=work,
3091
3133
  env=env,
3092
3134
  capture_output=True,
3093
3135
  text=True,
3136
+ encoding="utf-8",
3094
3137
  )
3095
3138
  if good_solo_headroom_probe.returncode != 0:
3096
3139
  print("risk probe covering solo-headroom command was rejected", file=sys.stderr)
@@ -3112,17 +3155,19 @@ def run_self_test() -> int:
3112
3155
  "required_files": ["bin/cli.js"],
3113
3156
  "forbidden_files": [],
3114
3157
  "max_deps_added": 0,
3115
- }) + "\n")
3158
+ }) + "\n", encoding="utf-8")
3116
3159
  spec_md.write_text(
3117
3160
  "# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n"
3118
3161
  "- solo-headroom hypothesis: solo_claude should miss duplicate handling.\n"
3119
- "- Observable command: `node check.js` exposes behavior.\n"
3162
+ "- Observable command: `node check.js` exposes behavior.\n",
3163
+ encoding="utf-8",
3120
3164
  )
3121
3165
  weak_sibling_solo = subprocess.run(
3122
3166
  [sys.executable, script_path, "--check-expected", str(expected_json)],
3123
3167
  cwd=work,
3124
3168
  capture_output=True,
3125
3169
  text=True,
3170
+ encoding="utf-8",
3126
3171
  )
3127
3172
  if weak_sibling_solo.returncode == 0:
3128
3173
  print("weak sibling solo-headroom hypothesis was accepted by --check-expected", file=sys.stderr)
@@ -3143,6 +3188,7 @@ def run_self_test() -> int:
3143
3188
  cwd=work,
3144
3189
  capture_output=True,
3145
3190
  text=True,
3191
+ encoding="utf-8",
3146
3192
  )
3147
3193
  if mismatched_sibling_solo.returncode == 0:
3148
3194
  print("mismatched sibling solo-headroom command was accepted by --check-expected", file=sys.stderr)
@@ -3163,6 +3209,7 @@ def run_self_test() -> int:
3163
3209
  cwd=work,
3164
3210
  capture_output=True,
3165
3211
  text=True,
3212
+ encoding="utf-8",
3166
3213
  )
3167
3214
  if matched_sibling_solo.returncode != 0:
3168
3215
  print("matched sibling solo-headroom command was rejected by --check-expected", file=sys.stderr)
@@ -3180,6 +3227,7 @@ def run_self_test() -> int:
3180
3227
  cwd=work,
3181
3228
  capture_output=True,
3182
3229
  text=True,
3230
+ encoding="utf-8",
3183
3231
  )
3184
3232
  if docs_style_sibling_solo.returncode != 0:
3185
3233
  print("docs-style sibling solo-headroom command was rejected by --check-expected", file=sys.stderr)
@@ -3196,6 +3244,7 @@ def run_self_test() -> int:
3196
3244
  cwd=work,
3197
3245
  capture_output=True,
3198
3246
  text=True,
3247
+ encoding="utf-8",
3199
3248
  )
3200
3249
  if weak_sibling_solo_ceiling.returncode == 0:
3201
3250
  print("weak sibling solo ceiling avoidance was accepted by --check-expected", file=sys.stderr)
@@ -3216,18 +3265,20 @@ def run_self_test() -> int:
3216
3265
  cwd=work,
3217
3266
  capture_output=True,
3218
3267
  text=True,
3268
+ encoding="utf-8",
3219
3269
  )
3220
3270
  if strong_sibling_solo_ceiling.returncode != 0:
3221
3271
  print("actionable sibling solo ceiling avoidance was rejected by --check-expected", file=sys.stderr)
3222
3272
  print(strong_sibling_solo_ceiling.stderr, file=sys.stderr)
3223
3273
  return 1
3224
3274
 
3225
- spec_md.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- probe must pass visible marker.\n")
3275
+ spec_md.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- probe must pass visible marker.\n", encoding="utf-8")
3226
3276
  expected_good = subprocess.run(
3227
3277
  [sys.executable, script_path, "--check-expected", str(expected_json)],
3228
3278
  cwd=work,
3229
3279
  capture_output=True,
3230
3280
  text=True,
3281
+ encoding="utf-8",
3231
3282
  )
3232
3283
  if expected_good.returncode != 0:
3233
3284
  print(expected_good.stderr, file=sys.stderr)
@@ -3242,6 +3293,7 @@ def run_self_test() -> int:
3242
3293
  cwd=work,
3243
3294
  capture_output=True,
3244
3295
  text=True,
3296
+ encoding="utf-8",
3245
3297
  )
3246
3298
  if expected_bad_sibling_complexity.returncode == 0:
3247
3299
  print("unsupported sibling spec complexity was accepted by --check-expected", file=sys.stderr)
@@ -3250,14 +3302,15 @@ def run_self_test() -> int:
3250
3302
  print("--check-expected did not report unsupported sibling spec complexity", file=sys.stderr)
3251
3303
  print(expected_bad_sibling_complexity.stderr, file=sys.stderr)
3252
3304
  return 1
3253
- spec_md.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- probe must pass visible marker.\n")
3305
+ spec_md.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- probe must pass visible marker.\n", encoding="utf-8")
3254
3306
 
3255
- expected_json.write_text(json.dumps({"verification_commands": []}) + "\n")
3307
+ expected_json.write_text(json.dumps({"verification_commands": []}) + "\n", encoding="utf-8")
3256
3308
  expected_empty_runtime = subprocess.run(
3257
3309
  [sys.executable, script_path, "--check-expected", str(expected_json)],
3258
3310
  cwd=work,
3259
3311
  capture_output=True,
3260
3312
  text=True,
3313
+ encoding="utf-8",
3261
3314
  )
3262
3315
  if expected_empty_runtime.returncode == 0:
3263
3316
  print("empty verification_commands should fail for runtime specs", file=sys.stderr)
@@ -3280,6 +3333,7 @@ def run_self_test() -> int:
3280
3333
  cwd=work,
3281
3334
  capture_output=True,
3282
3335
  text=True,
3336
+ encoding="utf-8",
3283
3337
  )
3284
3338
  if expected_empty_design.returncode != 0:
3285
3339
  print("empty verification_commands should be valid for pure-design specs", file=sys.stderr)
@@ -3298,6 +3352,7 @@ def run_self_test() -> int:
3298
3352
  cwd=work,
3299
3353
  capture_output=True,
3300
3354
  text=True,
3355
+ encoding="utf-8",
3301
3356
  )
3302
3357
  if pure_design_contradiction.returncode == 0:
3303
3358
  print("pure_design: true with non-empty verification_commands was accepted", file=sys.stderr)
@@ -3316,6 +3371,7 @@ def run_self_test() -> int:
3316
3371
  cwd=work,
3317
3372
  capture_output=True,
3318
3373
  text=True,
3374
+ encoding="utf-8",
3319
3375
  )
3320
3376
  if pure_design_not_boolean.returncode == 0:
3321
3377
  print("non-boolean pure_design was accepted", file=sys.stderr)
@@ -3325,12 +3381,13 @@ def run_self_test() -> int:
3325
3381
  print(pure_design_not_boolean.stderr, file=sys.stderr)
3326
3382
  return 1
3327
3383
 
3328
- expected_json.write_text(json.dumps({"unknown": True}) + "\n")
3384
+ expected_json.write_text(json.dumps({"unknown": True}) + "\n", encoding="utf-8")
3329
3385
  expected_bad = subprocess.run(
3330
3386
  [sys.executable, script_path, "--check-expected", str(expected_json)],
3331
3387
  cwd=work,
3332
3388
  capture_output=True,
3333
3389
  text=True,
3390
+ encoding="utf-8",
3334
3391
  )
3335
3392
  if expected_bad.returncode == 0:
3336
3393
  print("spec.expected.json with unknown key was accepted", file=sys.stderr)
@@ -3338,23 +3395,25 @@ def run_self_test() -> int:
3338
3395
 
3339
3396
  expected_json.write_text(json.dumps({
3340
3397
  "verification_commands": [{"cmd": "printf ok", "stdout_contians": ["ok"]}]
3341
- }) + "\n")
3398
+ }) + "\n", encoding="utf-8")
3342
3399
  expected_bad_command = subprocess.run(
3343
3400
  [sys.executable, script_path, "--check-expected", str(expected_json)],
3344
3401
  cwd=work,
3345
3402
  capture_output=True,
3346
3403
  text=True,
3404
+ encoding="utf-8",
3347
3405
  )
3348
3406
  if expected_bad_command.returncode == 0:
3349
3407
  print("spec.expected.json command with unknown key was accepted", file=sys.stderr)
3350
3408
  return 1
3351
3409
 
3352
- expected_json.write_text("[1]\n")
3410
+ expected_json.write_text("[1]\n", encoding="utf-8")
3353
3411
  expected_non_object = subprocess.run(
3354
3412
  [sys.executable, script_path, "--check-expected", str(expected_json)],
3355
3413
  cwd=work,
3356
3414
  capture_output=True,
3357
3415
  text=True,
3416
+ encoding="utf-8",
3358
3417
  )
3359
3418
  if expected_non_object.returncode == 0:
3360
3419
  print("spec.expected.json top-level array was accepted", file=sys.stderr)
@@ -3367,12 +3426,13 @@ def run_self_test() -> int:
3367
3426
  print("spec.expected.json top-level array produced a traceback", file=sys.stderr)
3368
3427
  return 1
3369
3428
 
3370
- expected_json.write_text("{broken\n")
3429
+ expected_json.write_text("{broken\n", encoding="utf-8")
3371
3430
  expected_invalid_json = subprocess.run(
3372
3431
  [sys.executable, script_path, "--check-expected", str(expected_json)],
3373
3432
  cwd=work,
3374
3433
  capture_output=True,
3375
3434
  text=True,
3435
+ encoding="utf-8",
3376
3436
  )
3377
3437
  if expected_invalid_json.returncode == 0:
3378
3438
  print("invalid spec.expected.json was accepted", file=sys.stderr)
@@ -3385,12 +3445,13 @@ def run_self_test() -> int:
3385
3445
  print("invalid spec.expected.json produced a traceback", file=sys.stderr)
3386
3446
  return 1
3387
3447
 
3388
- expected_json.write_text('{"verification_commands": NaN}\n')
3448
+ expected_json.write_text('{"verification_commands": NaN}\n', encoding="utf-8")
3389
3449
  expected_nan_json = subprocess.run(
3390
3450
  [sys.executable, script_path, "--check-expected", str(expected_json)],
3391
3451
  cwd=work,
3392
3452
  capture_output=True,
3393
3453
  text=True,
3454
+ encoding="utf-8",
3394
3455
  )
3395
3456
  if expected_nan_json.returncode == 0:
3396
3457
  print("NaN spec.expected.json was accepted", file=sys.stderr)
@@ -3426,6 +3487,7 @@ def run_self_test() -> int:
3426
3487
  )
3427
3488
  external_diff_base_sha = subprocess.check_output(
3428
3489
  ["git", "rev-parse", "HEAD"], cwd=external_diff_root, text=True,
3490
+ encoding="utf-8",
3429
3491
  ).strip()
3430
3492
  (external_diff_root / "outside.txt").write_text("worktree-only\n", encoding="utf-8")
3431
3493
  (external_diff_devlyn / "external-diff.patch").write_text(
@@ -3449,26 +3511,28 @@ def run_self_test() -> int:
3449
3511
  "base_ref": {"sha": external_diff_base_sha},
3450
3512
  }
3451
3513
  external_diff_state_path = external_diff_devlyn / "pipeline.state.json"
3452
- external_diff_state_path.write_text(json.dumps(external_diff_state) + "\n")
3514
+ external_diff_state_path.write_text(json.dumps(external_diff_state) + "\n", encoding="utf-8")
3453
3515
  external_diff_free_form = subprocess.run(
3454
3516
  [sys.executable, script_path],
3455
3517
  cwd=external_diff_root,
3456
3518
  capture_output=True,
3457
3519
  text=True,
3520
+ encoding="utf-8",
3458
3521
  )
3459
3522
  external_diff_findings_path = external_diff_devlyn / output_findings_name()
3460
3523
  external_diff_free_form_findings = (
3461
- external_diff_findings_path.read_text()
3524
+ external_diff_findings_path.read_text(encoding="utf-8")
3462
3525
  if external_diff_findings_path.is_file()
3463
3526
  else ""
3464
3527
  )
3465
3528
  external_diff_state["mode"] = "verify-only"
3466
- external_diff_state_path.write_text(json.dumps(external_diff_state) + "\n")
3529
+ external_diff_state_path.write_text(json.dumps(external_diff_state) + "\n", encoding="utf-8")
3467
3530
  external_diff_verify_only = subprocess.run(
3468
3531
  [sys.executable, script_path],
3469
3532
  cwd=external_diff_root,
3470
3533
  capture_output=True,
3471
3534
  text=True,
3535
+ encoding="utf-8",
3472
3536
  )
3473
3537
  if external_diff_free_form.returncode != 1:
3474
3538
  print("non-verify-only mode accepted .devlyn/external-diff.patch", file=sys.stderr)
@@ -3514,12 +3578,13 @@ def run_self_test() -> int:
3514
3578
  "spec_path": str(integrity_spec),
3515
3579
  "spec_sha256": "0" * 64,
3516
3580
  }
3517
- }))
3581
+ }), encoding="utf-8")
3518
3582
  spec_bad_hash_run = subprocess.run(
3519
3583
  [sys.executable, script_path],
3520
3584
  cwd=spec_integrity,
3521
3585
  capture_output=True,
3522
3586
  text=True,
3587
+ encoding="utf-8",
3523
3588
  )
3524
3589
  if spec_bad_hash_run.returncode == 0:
3525
3590
  print("spec source with mismatched source.spec_sha256 was accepted", file=sys.stderr)
@@ -3536,17 +3601,18 @@ def run_self_test() -> int:
3536
3601
  "spec_path": str(integrity_spec),
3537
3602
  "spec_sha256": spec_hash,
3538
3603
  }
3539
- }))
3604
+ }), encoding="utf-8")
3540
3605
  spec_hash_run = subprocess.run(
3541
3606
  [sys.executable, script_path],
3542
3607
  cwd=spec_integrity,
3543
3608
  capture_output=True,
3544
3609
  text=True,
3610
+ encoding="utf-8",
3545
3611
  )
3546
3612
  if spec_hash_run.returncode != 0:
3547
3613
  print(spec_hash_run.stderr, file=sys.stderr)
3548
3614
  return 1
3549
- staged_spec_hash = loads_strict_json((spec_integrity_devlyn / "spec-verify.json").read_text())
3615
+ staged_spec_hash = loads_strict_json((spec_integrity_devlyn / "spec-verify.json").read_text(encoding="utf-8"))
3550
3616
  if staged_spec_hash.get("verification_commands", [{}])[0].get("cmd") != "printf spec-hash-ok":
3551
3617
  print("spec source with matching source.spec_sha256 was not staged", file=sys.stderr)
3552
3618
  return 1
@@ -3564,12 +3630,13 @@ def run_self_test() -> int:
3564
3630
  )
3565
3631
  (generated_devlyn / "pipeline.state.json").write_text(json.dumps({
3566
3632
  "source": {"type": "generated", "criteria_path": str(generated_criteria)}
3567
- }))
3633
+ }), encoding="utf-8")
3568
3634
  generated_missing_hash_run = subprocess.run(
3569
3635
  [sys.executable, script_path],
3570
3636
  cwd=generated_user,
3571
3637
  capture_output=True,
3572
3638
  text=True,
3639
+ encoding="utf-8",
3573
3640
  )
3574
3641
  if generated_missing_hash_run.returncode == 0:
3575
3642
  print("generated criteria without source.criteria_sha256 was accepted", file=sys.stderr)
@@ -3585,12 +3652,13 @@ def run_self_test() -> int:
3585
3652
  "criteria_path": str(generated_criteria),
3586
3653
  "criteria_sha256": "0" * 64,
3587
3654
  }
3588
- }))
3655
+ }), encoding="utf-8")
3589
3656
  generated_bad_hash_run = subprocess.run(
3590
3657
  [sys.executable, script_path],
3591
3658
  cwd=generated_user,
3592
3659
  capture_output=True,
3593
3660
  text=True,
3661
+ encoding="utf-8",
3594
3662
  )
3595
3663
  if generated_bad_hash_run.returncode == 0:
3596
3664
  print("generated criteria with mismatched source.criteria_sha256 was accepted", file=sys.stderr)
@@ -3607,17 +3675,18 @@ def run_self_test() -> int:
3607
3675
  "criteria_path": str(generated_criteria),
3608
3676
  "criteria_sha256": generated_hash,
3609
3677
  }
3610
- }))
3678
+ }), encoding="utf-8")
3611
3679
  generated_run = subprocess.run(
3612
3680
  [sys.executable, script_path],
3613
3681
  cwd=generated_user,
3614
3682
  capture_output=True,
3615
3683
  text=True,
3684
+ encoding="utf-8",
3616
3685
  )
3617
3686
  if generated_run.returncode != 0:
3618
3687
  print(generated_run.stderr, file=sys.stderr)
3619
3688
  return 1
3620
- staged_generated = loads_strict_json((generated_devlyn / "spec-verify.json").read_text())
3689
+ staged_generated = loads_strict_json((generated_devlyn / "spec-verify.json").read_text(encoding="utf-8"))
3621
3690
  if staged_generated.get("verification_commands", [{}])[0].get("cmd") != "printf generated-ok":
3622
3691
  print("generated criteria carrier was not staged into .devlyn/spec-verify.json", file=sys.stderr)
3623
3692
  return 1
@@ -3633,12 +3702,13 @@ def run_self_test() -> int:
3633
3702
  "criteria_path": str(generated_criteria),
3634
3703
  "criteria_sha256": malformed_generated_hash,
3635
3704
  }
3636
- }))
3705
+ }), encoding="utf-8")
3637
3706
  malformed_generated_run = subprocess.run(
3638
3707
  [sys.executable, script_path],
3639
3708
  cwd=generated_user,
3640
3709
  capture_output=True,
3641
3710
  text=True,
3711
+ encoding="utf-8",
3642
3712
  )
3643
3713
  if malformed_generated_run.returncode == 0:
3644
3714
  print("generated criteria without a JSON carrier was accepted", file=sys.stderr)
@@ -3654,26 +3724,28 @@ def run_self_test() -> int:
3654
3724
  real_devlyn.mkdir()
3655
3725
  real_spec = real_user / "spec.md"
3656
3726
  real_spec.write_text(
3657
- "# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- sibling command must print sibling-ok.\n"
3727
+ "# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- sibling command must print sibling-ok.\n",
3728
+ encoding="utf-8",
3658
3729
  )
3659
3730
  (real_user / "spec.expected.json").write_text(json.dumps({
3660
3731
  "verification_commands": [
3661
3732
  {"cmd": "printf sibling-ok", "stdout_contains": ["sibling-ok"]}
3662
3733
  ]
3663
- }) + "\n")
3734
+ }) + "\n", encoding="utf-8")
3664
3735
  (real_devlyn / "pipeline.state.json").write_text(json.dumps({
3665
3736
  "source": {"type": "spec", "spec_path": str(real_spec)}
3666
- }))
3737
+ }), encoding="utf-8")
3667
3738
  sibling_run = subprocess.run(
3668
3739
  [sys.executable, script_path],
3669
3740
  cwd=real_user,
3670
3741
  capture_output=True,
3671
3742
  text=True,
3743
+ encoding="utf-8",
3672
3744
  )
3673
3745
  if sibling_run.returncode != 0:
3674
3746
  print(sibling_run.stderr, file=sys.stderr)
3675
3747
  return 1
3676
- staged = loads_strict_json((real_devlyn / "spec-verify.json").read_text())
3748
+ staged = loads_strict_json((real_devlyn / "spec-verify.json").read_text(encoding="utf-8"))
3677
3749
  if staged.get("verification_commands", [{}])[0].get("cmd") != "printf sibling-ok":
3678
3750
  print("sibling spec.expected.json was not staged into .devlyn/spec-verify.json", file=sys.stderr)
3679
3751
  return 1
@@ -3686,48 +3758,51 @@ def run_self_test() -> int:
3686
3758
  malformed_spec.write_text(
3687
3759
  "# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n```json\n"
3688
3760
  "{\"verification_commands\":[{\"cmd\":\"printf inline-ok\"}]}\n"
3689
- "```\n"
3761
+ "```\n",
3762
+ encoding="utf-8",
3690
3763
  )
3691
- (malformed / "spec.expected.json").write_text(json.dumps({"unknown": True}) + "\n")
3764
+ (malformed / "spec.expected.json").write_text(json.dumps({"unknown": True}) + "\n", encoding="utf-8")
3692
3765
  (malformed_devlyn / "pipeline.state.json").write_text(json.dumps({
3693
3766
  "source": {"type": "spec", "spec_path": str(malformed_spec)}
3694
- }))
3767
+ }), encoding="utf-8")
3695
3768
  malformed_run = subprocess.run(
3696
3769
  [sys.executable, script_path],
3697
3770
  cwd=malformed,
3698
3771
  capture_output=True,
3699
3772
  text=True,
3773
+ encoding="utf-8",
3700
3774
  )
3701
3775
  if malformed_run.returncode == 0:
3702
3776
  print("malformed sibling spec.expected.json fell back to inline carrier", file=sys.stderr)
3703
3777
  return 1
3704
3778
 
3705
3779
  bench_spec = work / "bench-spec.md"
3706
- bench_spec.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- benchmark pre-staged wins.\n")
3780
+ bench_spec.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- benchmark pre-staged wins.\n", encoding="utf-8")
3707
3781
  (work / "spec.expected.json").write_text(json.dumps({
3708
3782
  "verification_commands": [
3709
3783
  {"cmd": "printf wrong", "stdout_contains": ["wrong"]}
3710
3784
  ]
3711
- }) + "\n")
3785
+ }) + "\n", encoding="utf-8")
3712
3786
  (devlyn / "pipeline.state.json").write_text(json.dumps({
3713
3787
  "source": {"type": "spec", "spec_path": str(bench_spec)}
3714
- }))
3788
+ }), encoding="utf-8")
3715
3789
  (devlyn / "spec-verify.json").write_text(json.dumps({
3716
3790
  "verification_commands": [
3717
3791
  {"cmd": "printf bench-staged", "stdout_contains": ["bench-staged"]}
3718
3792
  ]
3719
- }) + "\n")
3793
+ }) + "\n", encoding="utf-8")
3720
3794
  bench_pre_staged = subprocess.run(
3721
3795
  [sys.executable, script_path],
3722
3796
  cwd=work,
3723
3797
  env=env,
3724
3798
  capture_output=True,
3725
3799
  text=True,
3800
+ encoding="utf-8",
3726
3801
  )
3727
3802
  if bench_pre_staged.returncode != 0:
3728
3803
  print(bench_pre_staged.stderr, file=sys.stderr)
3729
3804
  return 1
3730
- staged_bench = loads_strict_json((devlyn / "spec-verify.json").read_text())
3805
+ staged_bench = loads_strict_json((devlyn / "spec-verify.json").read_text(encoding="utf-8"))
3731
3806
  if staged_bench.get("verification_commands", [{}])[0].get("cmd") != "printf bench-staged":
3732
3807
  print("benchmark pre-staged contract was overwritten", file=sys.stderr)
3733
3808
  return 1
@@ -3737,15 +3812,15 @@ def run_self_test() -> int:
3737
3812
  verify_devlyn = verify_output / ".devlyn"
3738
3813
  verify_devlyn.mkdir()
3739
3814
  verify_spec = verify_output / "spec.md"
3740
- verify_spec.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- verify mechanical output.\n")
3815
+ verify_spec.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- verify mechanical output.\n", encoding="utf-8")
3741
3816
  (verify_output / "spec.expected.json").write_text(json.dumps({
3742
3817
  "verification_commands": [
3743
3818
  {"cmd": "printf wrong", "stdout_contains": ["expected"]}
3744
3819
  ]
3745
- }) + "\n")
3820
+ }) + "\n", encoding="utf-8")
3746
3821
  (verify_devlyn / "pipeline.state.json").write_text(json.dumps({
3747
3822
  "source": {"type": "spec", "spec_path": str(verify_spec)}
3748
- }))
3823
+ }), encoding="utf-8")
3749
3824
  verify_env = os.environ.copy()
3750
3825
  verify_env.update({
3751
3826
  "SPEC_VERIFY_PHASE": "verify_mechanical",
@@ -3758,11 +3833,12 @@ def run_self_test() -> int:
3758
3833
  env=verify_env,
3759
3834
  capture_output=True,
3760
3835
  text=True,
3836
+ encoding="utf-8",
3761
3837
  )
3762
3838
  if verify_output_run.returncode == 0:
3763
3839
  print("VERIFY output-mode failing command was accepted", file=sys.stderr)
3764
3840
  return 1
3765
- verify_findings = (verify_devlyn / "verify-mechanical.findings.jsonl").read_text()
3841
+ verify_findings = (verify_devlyn / "verify-mechanical.findings.jsonl").read_text(encoding="utf-8")
3766
3842
  if '"phase": "verify_mechanical"' not in verify_findings or "VERIFY-MECH-" not in verify_findings:
3767
3843
  print("VERIFY output-mode did not route findings to verify-mechanical", file=sys.stderr)
3768
3844
  return 1
@@ -3772,7 +3848,8 @@ def run_self_test() -> int:
3772
3848
  contract_devlyn = contract_root / ".devlyn"
3773
3849
  contract_devlyn.mkdir()
3774
3850
  (contract_root / "package.json").write_text(
3775
- '{\n "dependencies": {},\n "devDependencies": {}\n}\n'
3851
+ '{\n "dependencies": {},\n "devDependencies": {}\n}\n',
3852
+ encoding="utf-8",
3776
3853
  )
3777
3854
  subprocess.run(["git", "init", "-q"], cwd=contract_root, check=True)
3778
3855
  subprocess.run(["git", "add", "-A"], cwd=contract_root, check=True)
@@ -3785,14 +3862,16 @@ def run_self_test() -> int:
3785
3862
  ["git", "rev-parse", "HEAD"],
3786
3863
  cwd=contract_root,
3787
3864
  text=True,
3865
+ encoding="utf-8",
3788
3866
  ).strip()
3789
3867
  contract_spec = contract_root / "spec.md"
3790
- contract_spec.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- expected contract checks.\n")
3791
- (contract_root / "app.js").write_text("try { work(); } catch { return null; }\n")
3792
- (contract_root / "forbidden.txt").write_text("forbidden\n")
3868
+ contract_spec.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- expected contract checks.\n", encoding="utf-8")
3869
+ (contract_root / "app.js").write_text("try { work(); } catch { return null; }\n", encoding="utf-8")
3870
+ (contract_root / "forbidden.txt").write_text("forbidden\n", encoding="utf-8")
3793
3871
  (contract_root / "package.json").write_text(
3794
3872
  '{\n "dependencies": {\n "left-pad": "1.3.0"\n },\n'
3795
- ' "devDependencies": {}\n}\n'
3873
+ ' "devDependencies": {}\n}\n',
3874
+ encoding="utf-8",
3796
3875
  )
3797
3876
  (contract_root / "spec.expected.json").write_text(json.dumps({
3798
3877
  "verification_commands": [{"cmd": "printf ok", "stdout_contains": ["ok"]}],
@@ -3804,22 +3883,23 @@ def run_self_test() -> int:
3804
3883
  "required_files": ["required.txt"],
3805
3884
  "forbidden_files": ["forbidden.txt"],
3806
3885
  "max_deps_added": 0,
3807
- }) + "\n")
3886
+ }) + "\n", encoding="utf-8")
3808
3887
  subprocess.run(["git", "add", "-A"], cwd=contract_root, check=True)
3809
3888
  (contract_devlyn / "pipeline.state.json").write_text(json.dumps({
3810
3889
  "source": {"type": "spec", "spec_path": str(contract_spec)},
3811
3890
  "base_ref": {"sha": base_sha},
3812
- }))
3891
+ }), encoding="utf-8")
3813
3892
  contract_run = subprocess.run(
3814
3893
  [sys.executable, script_path],
3815
3894
  cwd=contract_root,
3816
3895
  capture_output=True,
3817
3896
  text=True,
3897
+ encoding="utf-8",
3818
3898
  )
3819
3899
  if contract_run.returncode == 0:
3820
3900
  print("expected contract violations were accepted", file=sys.stderr)
3821
3901
  return 1
3822
- findings_text = (contract_devlyn / output_findings_name()).read_text()
3902
+ findings_text = (contract_devlyn / output_findings_name()).read_text(encoding="utf-8")
3823
3903
  for rule_id in (
3824
3904
  "correctness.forbidden-pattern",
3825
3905
  "correctness.required-file-missing",
@@ -3837,13 +3917,14 @@ def run_self_test() -> int:
3837
3917
  "exit_code": 0,
3838
3918
  "tags": ["boundary_overlap"],
3839
3919
  "tag_evidence": {"boundary_overlap": ["one_minute_overlap"]},
3840
- }) + "\n")
3920
+ }) + "\n", encoding="utf-8")
3841
3921
  weak = subprocess.run(
3842
3922
  [sys.executable, script_path, "--validate-risk-probes"],
3843
3923
  cwd=work,
3844
3924
  env=env,
3845
3925
  capture_output=True,
3846
3926
  text=True,
3927
+ encoding="utf-8",
3847
3928
  )
3848
3929
  if weak.returncode == 0:
3849
3930
  print("incomplete boundary_overlap evidence was accepted", file=sys.stderr)
@@ -3857,11 +3938,12 @@ def run_self_test() -> int:
3857
3938
  error_spec.write_text(
3858
3939
  "# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n"
3859
3940
  "- Invalid input must print JSON error object `{ \"error\": \"bad_input\" }` to stderr and exit 2.\n"
3860
- "- Malformed input must exit 2 and print stderr JSON with keys `code` and `detail`; values are implementation-defined.\n"
3941
+ "- Malformed input must exit 2 and print stderr JSON with keys `code` and `detail`; values are implementation-defined.\n",
3942
+ encoding="utf-8",
3861
3943
  )
3862
3944
  (error_devlyn / "pipeline.state.json").write_text(json.dumps({
3863
3945
  "source": {"type": "spec", "spec_path": str(error_spec)}
3864
- }))
3946
+ }), encoding="utf-8")
3865
3947
  (error_devlyn / "risk-probes.jsonl").write_text(json.dumps({
3866
3948
  "id": "P6",
3867
3949
  "derived_from": "Invalid input must print a JSON error object to stderr and exit 2.",
@@ -3872,12 +3954,13 @@ def run_self_test() -> int:
3872
3954
  "stdout_stderr_contract": ["asserts_named_stream_output"],
3873
3955
  "error_contract": ["asserts_error_payload_or_stderr"],
3874
3956
  },
3875
- }) + "\n")
3957
+ }) + "\n", encoding="utf-8")
3876
3958
  weak_error_contract = subprocess.run(
3877
3959
  [sys.executable, script_path, "--validate-risk-probes"],
3878
3960
  cwd=error_root,
3879
3961
  capture_output=True,
3880
3962
  text=True,
3963
+ encoding="utf-8",
3881
3964
  )
3882
3965
  if weak_error_contract.returncode == 0:
3883
3966
  print("error_contract without exit-code evidence was accepted", file=sys.stderr)
@@ -3896,12 +3979,13 @@ def run_self_test() -> int:
3896
3979
  "asserts_nonzero_or_exit_2",
3897
3980
  ],
3898
3981
  },
3899
- }) + "\n")
3982
+ }) + "\n", encoding="utf-8")
3900
3983
  weak_stdio_contract = subprocess.run(
3901
3984
  [sys.executable, script_path, "--validate-risk-probes"],
3902
3985
  cwd=error_root,
3903
3986
  capture_output=True,
3904
3987
  text=True,
3988
+ encoding="utf-8",
3905
3989
  )
3906
3990
  if weak_stdio_contract.returncode == 0:
3907
3991
  print("stdout_stderr_contract without stream evidence was accepted", file=sys.stderr)
@@ -3926,12 +4010,13 @@ def run_self_test() -> int:
3926
4010
  "visible_text_names_exact_json_error_object",
3927
4011
  ],
3928
4012
  },
3929
- }) + "\n")
4013
+ }) + "\n", encoding="utf-8")
3930
4014
  missing_exact_error_object = subprocess.run(
3931
4015
  [sys.executable, script_path, "--validate-risk-probes"],
3932
4016
  cwd=error_root,
3933
4017
  capture_output=True,
3934
4018
  text=True,
4019
+ encoding="utf-8",
3935
4020
  )
3936
4021
  if missing_exact_error_object.returncode == 0:
3937
4022
  print(
@@ -3961,12 +4046,13 @@ def run_self_test() -> int:
3961
4046
  "asserts_exact_error_object",
3962
4047
  ],
3963
4048
  },
3964
- }) + "\n")
4049
+ }) + "\n", encoding="utf-8")
3965
4050
  strong_json_error_shape_contract = subprocess.run(
3966
4051
  [sys.executable, script_path, "--validate-risk-probes"],
3967
4052
  cwd=error_root,
3968
4053
  capture_output=True,
3969
4054
  text=True,
4055
+ encoding="utf-8",
3970
4056
  )
3971
4057
  if strong_json_error_shape_contract.returncode != 0:
3972
4058
  print("JSON error object shape_contract with exact object evidence was rejected", file=sys.stderr)
@@ -3993,12 +4079,13 @@ def run_self_test() -> int:
3993
4079
  "asserts_no_unexpected_output_keys",
3994
4080
  ],
3995
4081
  },
3996
- }) + "\n")
4082
+ }) + "\n", encoding="utf-8")
3997
4083
  error_exit_shape_contract = subprocess.run(
3998
4084
  [sys.executable, script_path, "--validate-risk-probes"],
3999
4085
  cwd=error_root,
4000
4086
  capture_output=True,
4001
4087
  text=True,
4088
+ encoding="utf-8",
4002
4089
  )
4003
4090
  if error_exit_shape_contract.returncode != 0:
4004
4091
  print("error-exit shape_contract without visible exact error object was rejected", file=sys.stderr)
@@ -4012,11 +4099,12 @@ def run_self_test() -> int:
4012
4099
  http_error_spec = http_error_root / "spec.md"
4013
4100
  http_error_spec.write_text(
4014
4101
  "# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n"
4015
- "- An invalid query returns HTTP 400 with JSON error body `{ \"error\": \"invalid_query\", \"field\": \"per_page\" }`.\n"
4102
+ "- An invalid query returns HTTP 400 with JSON error body `{ \"error\": \"invalid_query\", \"field\": \"per_page\" }`.\n",
4103
+ encoding="utf-8",
4016
4104
  )
4017
4105
  (http_error_devlyn / "pipeline.state.json").write_text(json.dumps({
4018
4106
  "source": {"type": "spec", "spec_path": str(http_error_spec)}
4019
- }))
4107
+ }), encoding="utf-8")
4020
4108
  (http_error_devlyn / "risk-probes.jsonl").write_text(json.dumps({
4021
4109
  "id": "P8b",
4022
4110
  "derived_from": (
@@ -4029,12 +4117,13 @@ def run_self_test() -> int:
4029
4117
  "tag_evidence": {
4030
4118
  "http_error_contract": ["asserts_http_error_status"],
4031
4119
  },
4032
- }) + "\n")
4120
+ }) + "\n", encoding="utf-8")
4033
4121
  incomplete_http_error_contract = subprocess.run(
4034
4122
  [sys.executable, script_path, "--validate-risk-probes"],
4035
4123
  cwd=http_error_root,
4036
4124
  capture_output=True,
4037
4125
  text=True,
4126
+ encoding="utf-8",
4038
4127
  )
4039
4128
  if incomplete_http_error_contract.returncode == 0:
4040
4129
  print("http_error_contract without payload evidence was accepted", file=sys.stderr)
@@ -4061,12 +4150,13 @@ def run_self_test() -> int:
4061
4150
  "visible_text_names_exact_json_error_object",
4062
4151
  ],
4063
4152
  },
4064
- }) + "\n")
4153
+ }) + "\n", encoding="utf-8")
4065
4154
  weak_exact_error_shape_contract = subprocess.run(
4066
4155
  [sys.executable, script_path, "--validate-risk-probes"],
4067
4156
  cwd=http_error_root,
4068
4157
  capture_output=True,
4069
4158
  text=True,
4159
+ encoding="utf-8",
4070
4160
  )
4071
4161
  if weak_exact_error_shape_contract.returncode == 0:
4072
4162
  print("exact error body shape_contract without exact object evidence was accepted", file=sys.stderr)
@@ -4094,12 +4184,13 @@ def run_self_test() -> int:
4094
4184
  "asserts_exact_error_object",
4095
4185
  ],
4096
4186
  },
4097
- }) + "\n")
4187
+ }) + "\n", encoding="utf-8")
4098
4188
  strong_exact_error_shape_contract = subprocess.run(
4099
4189
  [sys.executable, script_path, "--validate-risk-probes"],
4100
4190
  cwd=http_error_root,
4101
4191
  capture_output=True,
4102
4192
  text=True,
4193
+ encoding="utf-8",
4103
4194
  )
4104
4195
  if strong_exact_error_shape_contract.returncode != 0:
4105
4196
  print("exact error body shape_contract with exact object evidence was rejected", file=sys.stderr)
@@ -4114,11 +4205,12 @@ def run_self_test() -> int:
4114
4205
  shape_spec.write_text(
4115
4206
  "# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n"
4116
4207
  "- On success, output is one JSON object with keys `applied`, `rejected`, and `accounts`; "
4117
- "`rejected` rows have keys `id` and `reason`.\n"
4208
+ "`rejected` rows have keys `id` and `reason`.\n",
4209
+ encoding="utf-8",
4118
4210
  )
4119
4211
  (shape_devlyn / "pipeline.state.json").write_text(json.dumps({
4120
4212
  "source": {"type": "spec", "spec_path": str(shape_spec)}
4121
- }))
4213
+ }), encoding="utf-8")
4122
4214
  (shape_devlyn / "risk-probes.jsonl").write_text(json.dumps({
4123
4215
  "id": "P8e",
4124
4216
  "derived_from": (
@@ -4129,12 +4221,13 @@ def run_self_test() -> int:
4129
4221
  "exit_code": 0,
4130
4222
  "tags": ["shape_contract"],
4131
4223
  "tag_evidence": {},
4132
- }) + "\n")
4224
+ }) + "\n", encoding="utf-8")
4133
4225
  weak_shape_contract = subprocess.run(
4134
4226
  [sys.executable, script_path, "--validate-risk-probes"],
4135
4227
  cwd=shape_root,
4136
4228
  capture_output=True,
4137
4229
  text=True,
4230
+ encoding="utf-8",
4138
4231
  )
4139
4232
  if weak_shape_contract.returncode == 0:
4140
4233
  print("shape_contract without any evidence was accepted", file=sys.stderr)
@@ -4156,12 +4249,13 @@ def run_self_test() -> int:
4156
4249
  "asserts_no_unexpected_output_keys",
4157
4250
  ],
4158
4251
  },
4159
- }) + "\n")
4252
+ }) + "\n", encoding="utf-8")
4160
4253
  strong_shape_contract = subprocess.run(
4161
4254
  [sys.executable, script_path, "--validate-risk-probes"],
4162
4255
  cwd=shape_root,
4163
4256
  capture_output=True,
4164
4257
  text=True,
4258
+ encoding="utf-8",
4165
4259
  )
4166
4260
  if strong_shape_contract.returncode != 0:
4167
4261
  print("shape_contract with exact key evidence was rejected", file=sys.stderr)
@@ -4181,7 +4275,8 @@ def run_self_test() -> int:
4181
4275
  required_spec.write_text(
4182
4276
  "# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n"
4183
4277
  "- A failed all-or-nothing operation must roll back tentative state "
4184
- "so later orders can use the released stock.\n"
4278
+ "so later orders can use the released stock.\n",
4279
+ encoding="utf-8",
4185
4280
  )
4186
4281
  (required_root / "spec.expected.json").write_text(json.dumps({
4187
4282
  "verification_commands": [{"cmd": "printf ok", "stdout_contains": ["ok"]}],
@@ -4194,10 +4289,10 @@ def run_self_test() -> int:
4194
4289
  ),
4195
4290
  },
4196
4291
  ],
4197
- }) + "\n")
4292
+ }) + "\n", encoding="utf-8")
4198
4293
  (required_devlyn / "pipeline.state.json").write_text(json.dumps({
4199
4294
  "source": {"type": "spec", "spec_path": str(required_spec)}
4200
- }))
4295
+ }), encoding="utf-8")
4201
4296
  (required_devlyn / "risk-probes.jsonl").write_text(json.dumps({
4202
4297
  "id": "P14",
4203
4298
  "derived_from": (
@@ -4213,12 +4308,13 @@ def run_self_test() -> int:
4213
4308
  "later_entity_fails_or_reroutes",
4214
4309
  ],
4215
4310
  },
4216
- }) + "\n")
4311
+ }) + "\n", encoding="utf-8")
4217
4312
  missing_declared_requirement = subprocess.run(
4218
4313
  [sys.executable, script_path, "--validate-risk-probes"],
4219
4314
  cwd=required_root,
4220
4315
  capture_output=True,
4221
4316
  text=True,
4317
+ encoding="utf-8",
4222
4318
  )
4223
4319
  if missing_declared_requirement.returncode == 0:
4224
4320
  print(
@@ -4250,12 +4346,13 @@ def run_self_test() -> int:
4250
4346
  "later_entity_uses_released_state",
4251
4347
  ],
4252
4348
  },
4253
- }) + "\n")
4349
+ }) + "\n", encoding="utf-8")
4254
4350
  covered_declared_requirement = subprocess.run(
4255
4351
  [sys.executable, script_path, "--validate-risk-probes"],
4256
4352
  cwd=required_root,
4257
4353
  capture_output=True,
4258
4354
  text=True,
4355
+ encoding="utf-8",
4259
4356
  )
4260
4357
  if covered_declared_requirement.returncode != 0:
4261
4358
  print(
@@ -4271,12 +4368,13 @@ def run_self_test() -> int:
4271
4368
  "required_risk_probe_requirements": [
4272
4369
  {"tag": "not-a-real-tag", "derived_from": "irrelevant"},
4273
4370
  ],
4274
- }) + "\n")
4371
+ }) + "\n", encoding="utf-8")
4275
4372
  malformed_requirement_tag = subprocess.run(
4276
4373
  [sys.executable, script_path, "--validate-risk-probes"],
4277
4374
  cwd=required_root,
4278
4375
  capture_output=True,
4279
4376
  text=True,
4377
+ encoding="utf-8",
4280
4378
  )
4281
4379
  if malformed_requirement_tag.returncode == 0:
4282
4380
  print("required_risk_probe_requirements with an unknown tag was accepted", file=sys.stderr)
@@ -4287,12 +4385,13 @@ def run_self_test() -> int:
4287
4385
  "required_risk_probe_requirements": [
4288
4386
  {"tag": "rollback_state", "derived_from": "text not present in the spec"},
4289
4387
  ],
4290
- }) + "\n")
4388
+ }) + "\n", encoding="utf-8")
4291
4389
  malformed_requirement_derived_from = subprocess.run(
4292
4390
  [sys.executable, script_path, "--validate-risk-probes"],
4293
4391
  cwd=required_root,
4294
4392
  capture_output=True,
4295
4393
  text=True,
4394
+ encoding="utf-8",
4296
4395
  )
4297
4396
  if malformed_requirement_derived_from.returncode == 0:
4298
4397
  print(
@@ -4310,11 +4409,12 @@ def run_self_test() -> int:
4310
4409
  atomic_batch_spec.write_text(
4311
4410
  "# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n"
4312
4411
  "- A POST with one valid + one invalid item returns `400`, AND a subsequent GET returns the same list as before the import.\n"
4313
- "- A POST with all-valid items returns `201`, and the items appear in GET output in order with distinct ids.\n"
4412
+ "- A POST with all-valid items returns `201`, and the items appear in GET output in order with distinct ids.\n",
4413
+ encoding="utf-8",
4314
4414
  )
4315
4415
  (atomic_batch_devlyn / "pipeline.state.json").write_text(json.dumps({
4316
4416
  "source": {"type": "spec", "spec_path": str(atomic_batch_spec)}
4317
- }))
4417
+ }), encoding="utf-8")
4318
4418
  (atomic_batch_devlyn / "risk-probes.jsonl").write_text(json.dumps({
4319
4419
  "id": "P13b",
4320
4420
  "derived_from": (
@@ -4330,12 +4430,13 @@ def run_self_test() -> int:
4330
4430
  "asserts_store_unchanged_after_failure",
4331
4431
  ],
4332
4432
  },
4333
- }) + "\n")
4433
+ }) + "\n", encoding="utf-8")
4334
4434
  incomplete_atomic_batch_probe = subprocess.run(
4335
4435
  [sys.executable, script_path, "--validate-risk-probes"],
4336
4436
  cwd=atomic_batch_root,
4337
4437
  capture_output=True,
4338
4438
  text=True,
4439
+ encoding="utf-8",
4339
4440
  )
4340
4441
  if incomplete_atomic_batch_probe.returncode == 0:
4341
4442
  print("atomic_batch_state without success-order evidence was accepted", file=sys.stderr)
@@ -4350,15 +4451,15 @@ def run_self_test() -> int:
4350
4451
  (scope_root / "lib").mkdir()
4351
4452
  (scope_root / "lib2").mkdir()
4352
4453
  (scope_root / "data").mkdir()
4353
- (scope_root / "bin" / "cli.js").write_text("module.exports = {};\n")
4354
- (scope_root / "lib" / "keep.js").write_text("module.exports = {};\n")
4355
- (scope_root / "lib2" / "keep.js").write_text("module.exports = {};\n")
4356
- (scope_root / "data" / "usage-stats.json").write_text("{}\n")
4454
+ (scope_root / "bin" / "cli.js").write_text("module.exports = {};\n", encoding="utf-8")
4455
+ (scope_root / "lib" / "keep.js").write_text("module.exports = {};\n", encoding="utf-8")
4456
+ (scope_root / "lib2" / "keep.js").write_text("module.exports = {};\n", encoding="utf-8")
4457
+ (scope_root / "data" / "usage-stats.json").write_text("{}\n", encoding="utf-8")
4357
4458
  scope_spec = scope_root / "spec.md"
4358
- scope_spec.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- scope gate checks.\n")
4459
+ scope_spec.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- scope gate checks.\n", encoding="utf-8")
4359
4460
  (scope_root / "spec.expected.json").write_text(json.dumps({
4360
4461
  "verification_commands": [{"cmd": "printf ok", "stdout_contains": ["ok"]}],
4361
- }) + "\n")
4462
+ }) + "\n", encoding="utf-8")
4362
4463
  subprocess.run(["git", "init", "-q"], cwd=scope_root, check=True)
4363
4464
  subprocess.run(["git", "add", "-A"], cwd=scope_root, check=True)
4364
4465
  subprocess.run(
@@ -4368,11 +4469,12 @@ def run_self_test() -> int:
4368
4469
  )
4369
4470
  scope_base_sha = subprocess.check_output(
4370
4471
  ["git", "rev-parse", "HEAD"], cwd=scope_root, text=True,
4472
+ encoding="utf-8",
4371
4473
  ).strip()
4372
4474
  (scope_devlyn / "pipeline.state.json").write_text(json.dumps({
4373
4475
  "source": {"type": "spec", "spec_path": str(scope_spec)},
4374
4476
  "base_ref": {"sha": scope_base_sha},
4375
- }))
4477
+ }), encoding="utf-8")
4376
4478
  (scope_root / "preexisting.local").write_text("pre-existing untracked\n", encoding="utf-8")
4377
4479
  # Writer parity: --write-untracked-baseline must share the reader's
4378
4480
  # parser — untracked DIRECTORIES expand to per-file paths and
@@ -4384,6 +4486,7 @@ def run_self_test() -> int:
4384
4486
  write_baseline_run = subprocess.run(
4385
4487
  [sys.executable, script_path, "--write-untracked-baseline"],
4386
4488
  cwd=scope_root, capture_output=True, text=True,
4489
+ encoding="utf-8",
4387
4490
  )
4388
4491
  if write_baseline_run.returncode != 0:
4389
4492
  print("--write-untracked-baseline failed", file=sys.stderr)
@@ -4399,30 +4502,33 @@ def run_self_test() -> int:
4399
4502
  scope_build_gate_env["SPEC_VERIFY_PHASE"] = "build_gate"
4400
4503
 
4401
4504
  # Test 1: no .devlyn/plan.md at all -> fail-closed CRITICAL, not a no-op.
4402
- (scope_root / "bin" / "cli.js").write_text("module.exports = { ok: true };\n")
4505
+ (scope_root / "bin" / "cli.js").write_text("module.exports = { ok: true };\n", encoding="utf-8")
4403
4506
  missing_plan_run = subprocess.run(
4404
4507
  [sys.executable, script_path], cwd=scope_root, env=scope_build_gate_env,
4405
4508
  capture_output=True, text=True,
4509
+ encoding="utf-8",
4406
4510
  )
4407
4511
  if missing_plan_run.returncode == 0:
4408
4512
  print("BUILD_GATE accepted a run with no plan.md", file=sys.stderr)
4409
4513
  return 1
4410
- if "scope.authorized-surface-malformed" not in scope_findings_path.read_text():
4514
+ if "scope.authorized-surface-malformed" not in scope_findings_path.read_text(encoding="utf-8"):
4411
4515
  print("missing plan.md did not emit scope.authorized-surface-malformed", file=sys.stderr)
4412
4516
  return 1
4413
4517
 
4414
4518
  # Test 2: plan.md present but no authorized_surface json block -> malformed.
4415
4519
  (scope_devlyn / "plan.md").write_text(
4416
- "# PLAN\n\n<!-- devlyn:authorized-surface -->\n## 1. Files to touch\n\n- `bin/cli.js` (edit): ship the fix.\n"
4520
+ "# PLAN\n\n<!-- devlyn:authorized-surface -->\n## 1. Files to touch\n\n- `bin/cli.js` (edit): ship the fix.\n",
4521
+ encoding="utf-8",
4417
4522
  )
4418
4523
  malformed_block_run = subprocess.run(
4419
4524
  [sys.executable, script_path], cwd=scope_root, env=scope_build_gate_env,
4420
4525
  capture_output=True, text=True,
4526
+ encoding="utf-8",
4421
4527
  )
4422
4528
  if malformed_block_run.returncode == 0:
4423
4529
  print("BUILD_GATE accepted plan.md with no authorized_surface block", file=sys.stderr)
4424
4530
  return 1
4425
- if "scope.authorized-surface-malformed" not in scope_findings_path.read_text():
4531
+ if "scope.authorized-surface-malformed" not in scope_findings_path.read_text(encoding="utf-8"):
4426
4532
  print("missing authorized_surface block did not emit scope.authorized-surface-malformed", file=sys.stderr)
4427
4533
  return 1
4428
4534
  malformed_print_surface = subprocess.run(
@@ -4438,15 +4544,18 @@ def run_self_test() -> int:
4438
4544
  (scope_devlyn / "plan.md").write_text(
4439
4545
  "# PLAN\n\n<!-- devlyn:authorized-surface -->\n## 1. Files to touch\n\n"
4440
4546
  "```json\n"
4441
- + json.dumps({"authorized_surface": [entry]}) + "\n```\n"
4547
+ + json.dumps({"authorized_surface": [entry]}) + "\n```\n",
4548
+ encoding="utf-8",
4442
4549
  )
4443
4550
  brace_build_gate = subprocess.run(
4444
4551
  [sys.executable, script_path], cwd=scope_root, env=scope_build_gate_env,
4445
4552
  capture_output=True, text=True,
4553
+ encoding="utf-8",
4446
4554
  )
4447
4555
  brace_print_surface = subprocess.run(
4448
4556
  [sys.executable, script_path, "--print-authorized-surface"], cwd=scope_root,
4449
4557
  capture_output=True, text=True,
4558
+ encoding="utf-8",
4450
4559
  )
4451
4560
  findings_text = scope_findings_path.read_text(encoding="utf-8")
4452
4561
  if (
@@ -4466,9 +4575,10 @@ def run_self_test() -> int:
4466
4575
  "- `bin/cli.js` (edit): ship the fix.\n\n"
4467
4576
  "```json\n"
4468
4577
  '{"authorized_surface": ["bin/cli.js", "lib/**", "authorized-but-uncreated.txt"]}\n'
4469
- "```\n"
4578
+ "```\n",
4579
+ encoding="utf-8",
4470
4580
  )
4471
- (scope_root / "lib" / "new.js").write_text("module.exports = { created: true };\n")
4581
+ (scope_root / "lib" / "new.js").write_text("module.exports = { created: true };\n", encoding="utf-8")
4472
4582
  print_surface = subprocess.run(
4473
4583
  [sys.executable, script_path, "--print-authorized-surface"],
4474
4584
  cwd=scope_root,
@@ -4490,16 +4600,18 @@ def run_self_test() -> int:
4490
4600
  missing_baseline_run = subprocess.run(
4491
4601
  [sys.executable, script_path], cwd=scope_root, env=scope_build_gate_env,
4492
4602
  capture_output=True, text=True,
4603
+ encoding="utf-8",
4493
4604
  )
4494
4605
  if missing_baseline_run.returncode == 0:
4495
4606
  print("BUILD_GATE accepted missing .devlyn/untracked.baseline", file=sys.stderr)
4496
4607
  return 1
4497
- if "untracked.baseline" not in scope_findings_path.read_text():
4608
+ if "untracked.baseline" not in scope_findings_path.read_text(encoding="utf-8"):
4498
4609
  print("missing untracked baseline did not emit a scope finding", file=sys.stderr)
4499
4610
  return 1
4500
4611
  rewrite_baseline_run = subprocess.run(
4501
4612
  [sys.executable, script_path, "--write-untracked-baseline"],
4502
4613
  cwd=scope_root, capture_output=True, text=True,
4614
+ encoding="utf-8",
4503
4615
  )
4504
4616
  if rewrite_baseline_run.returncode != 0:
4505
4617
  print("--write-untracked-baseline re-run failed", file=sys.stderr)
@@ -4507,29 +4619,31 @@ def run_self_test() -> int:
4507
4619
  in_scope_run = subprocess.run(
4508
4620
  [sys.executable, script_path], cwd=scope_root, env=scope_build_gate_env,
4509
4621
  capture_output=True, text=True,
4622
+ encoding="utf-8",
4510
4623
  )
4511
4624
  if in_scope_run.returncode != 0:
4512
4625
  print("in-scope-only diff was rejected", file=sys.stderr)
4513
4626
  print(in_scope_run.stderr, file=sys.stderr)
4514
4627
  return 1
4515
- if "scope." in scope_findings_path.read_text():
4628
+ if "scope." in scope_findings_path.read_text(encoding="utf-8"):
4516
4629
  print("in-scope-only diff produced a spurious scope finding", file=sys.stderr)
4517
- print(scope_findings_path.read_text(), file=sys.stderr)
4630
+ print(scope_findings_path.read_text(encoding="utf-8"), file=sys.stderr)
4518
4631
  return 1
4519
4632
 
4520
4633
  # Test 4: directory grant covers lib/**; an out-of-scope file must be
4521
4634
  # flagged, and the fix_hint must never suggest self-authorization.
4522
- (scope_root / "lib" / "keep.js").write_text("module.exports = { touched: true };\n")
4523
- (scope_root / "data" / "usage-stats.json").write_text('{"leaked": true}\n')
4524
- (scope_root / "data" / "scratch.json").write_text('{"untracked": true}\n')
4635
+ (scope_root / "lib" / "keep.js").write_text("module.exports = { touched: true };\n", encoding="utf-8")
4636
+ (scope_root / "data" / "usage-stats.json").write_text('{"leaked": true}\n', encoding="utf-8")
4637
+ (scope_root / "data" / "scratch.json").write_text('{"untracked": true}\n', encoding="utf-8")
4525
4638
  out_of_scope_run = subprocess.run(
4526
4639
  [sys.executable, script_path], cwd=scope_root, env=scope_build_gate_env,
4527
4640
  capture_output=True, text=True,
4641
+ encoding="utf-8",
4528
4642
  )
4529
4643
  if out_of_scope_run.returncode == 0:
4530
4644
  print("out-of-scope file was accepted", file=sys.stderr)
4531
4645
  return 1
4532
- out_of_scope_lines = scope_findings_path.read_text().splitlines()
4646
+ out_of_scope_lines = scope_findings_path.read_text(encoding="utf-8").splitlines()
4533
4647
  out_of_scope_findings = [loads_strict_json(line) for line in out_of_scope_lines if line.strip()]
4534
4648
  flagged_files = {f["file"] for f in out_of_scope_findings if f.get("rule_id") == "scope.out-of-scope-file"}
4535
4649
  if "data/usage-stats.json" not in flagged_files:
@@ -4558,14 +4672,15 @@ def run_self_test() -> int:
4558
4672
 
4559
4673
  # Test 5: lib2/keep.js must NOT be covered by the lib/** grant
4560
4674
  # (directory-prefix boundary, not a bare string-prefix match).
4561
- (scope_root / "lib2" / "keep.js").write_text("module.exports = { touched: true };\n")
4675
+ (scope_root / "lib2" / "keep.js").write_text("module.exports = { touched: true };\n", encoding="utf-8")
4562
4676
  subprocess.run(
4563
4677
  [sys.executable, script_path], cwd=scope_root, env=scope_build_gate_env,
4564
4678
  capture_output=True, text=True,
4679
+ encoding="utf-8",
4565
4680
  )
4566
4681
  boundary_flagged = {
4567
4682
  loads_strict_json(line)["file"]
4568
- for line in scope_findings_path.read_text().splitlines() if line.strip()
4683
+ for line in scope_findings_path.read_text(encoding="utf-8").splitlines() if line.strip()
4569
4684
  }
4570
4685
  if "lib2/keep.js" not in boundary_flagged:
4571
4686
  print("lib/** incorrectly matched lib2/keep.js (directory-prefix boundary bug)", file=sys.stderr)
@@ -4585,8 +4700,9 @@ def run_self_test() -> int:
4585
4700
  subprocess.run(
4586
4701
  [sys.executable, script_path], cwd=scope_root, env=verify_mech_env,
4587
4702
  capture_output=True, text=True,
4703
+ encoding="utf-8",
4588
4704
  )
4589
- verify_mech_findings = (scope_devlyn / "verify-mechanical.findings.jsonl").read_text()
4705
+ verify_mech_findings = (scope_devlyn / "verify-mechanical.findings.jsonl").read_text(encoding="utf-8")
4590
4706
  if "scope." in verify_mech_findings:
4591
4707
  print("VERIFY MECHANICAL ran the BUILD_GATE-only authorized_surface gate", file=sys.stderr)
4592
4708
  print(verify_mech_findings, file=sys.stderr)
@@ -4850,7 +4966,7 @@ def main() -> int:
4850
4966
  return 0
4851
4967
  else:
4852
4968
  try:
4853
- spec = loads_strict_json(spec_path.read_text())
4969
+ spec = loads_strict_json(spec_path.read_text(encoding="utf-8"))
4854
4970
  except (ValueError, OSError) as e:
4855
4971
  print(f"[spec-verify] error: cannot parse {spec_path}: {e}", file=sys.stderr)
4856
4972
  return 2
@@ -5145,12 +5261,12 @@ def main() -> int:
5145
5261
  results_path.write_text(json.dumps({
5146
5262
  "commands": results,
5147
5263
  "process_evidence": evidence_carrier,
5148
- }, indent=2) + "\n")
5264
+ }, indent=2) + "\n", encoding="utf-8")
5149
5265
 
5150
5266
  # Append findings (jsonl). BUILD_GATE merge step concatenates this onto
5151
5267
  # build_gate.findings.jsonl; never overwrite the orchestrator's own gate
5152
5268
  # findings. Truncate this file each run since it is a per-round artifact.
5153
- with findings_path.open("w") as fh:
5269
+ with findings_path.open("w", encoding="utf-8") as fh:
5154
5270
  for f in findings:
5155
5271
  fh.write(json.dumps(f) + "\n")
5156
5272
 
@@ -5181,4 +5297,5 @@ def main() -> int:
5181
5297
 
5182
5298
 
5183
5299
  if __name__ == "__main__":
5300
+ runpy.run_path(str(Path(__file__).with_name("platform-support.py")))["configure_utf8"]()
5184
5301
  sys.exit(main())