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.
- package/README.md +6 -1
- package/bin/devlyn.js +69 -23
- package/config/skills/_shared/adapters/claude.md +3 -2
- package/config/skills/_shared/adapters/codex.md +1 -1
- package/config/skills/_shared/archive_run.py +5 -3
- package/config/skills/_shared/codex-config.md +13 -8
- package/config/skills/_shared/codex-monitored.sh +12 -19
- package/config/skills/_shared/collect-codex-findings.py +1 -0
- package/config/skills/_shared/engine-preflight.md +2 -0
- package/config/skills/_shared/finish-gate.py +4 -1
- package/config/skills/_shared/invocation-receipt.py +194 -5
- package/config/skills/_shared/judge-role-evidence.py +33 -6
- package/config/skills/_shared/phase-prompt-render.py +2 -0
- package/config/skills/_shared/platform-support.py +370 -0
- package/config/skills/_shared/process-evidence.py +4 -2
- package/config/skills/_shared/resolve-bootstrap.py +102 -59
- package/config/skills/_shared/resolve-stop-hook.py +3 -1
- package/config/skills/_shared/role-config.py +7 -5
- package/config/skills/_shared/run-bounded.py +35 -21
- package/config/skills/_shared/spec-verify-check.py +274 -157
- package/config/skills/_shared/state-phase-write.py +26 -9
- package/config/skills/_shared/task-complete.py +87 -83
- package/config/skills/_shared/verify-merge-findings.py +9 -8
- package/config/skills/devlyn:resolve/SKILL.md +10 -7
- package/config/skills/devlyn:resolve/references/phases/verify.md +5 -3
- package/config/skills/devlyn:resolve/references/task-completion.md +1 -1
- package/package.json +1 -1
- package/scripts/lint-skills.sh +6 -5
|
@@ -8,7 +8,6 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import argparse
|
|
10
10
|
import contextlib
|
|
11
|
-
import fcntl
|
|
12
11
|
import functools
|
|
13
12
|
import hashlib
|
|
14
13
|
import json
|
|
@@ -35,7 +34,7 @@ def require(condition, message):
|
|
|
35
34
|
|
|
36
35
|
|
|
37
36
|
def command(argv, *, cwd=None, ok=(0,)):
|
|
38
|
-
result = subprocess.run(argv, cwd=cwd, capture_output=True, text=True)
|
|
37
|
+
result = subprocess.run(argv, cwd=cwd, capture_output=True, text=True, encoding="utf-8")
|
|
39
38
|
require(result.returncode in ok, f"{shlex.join(argv[:4])}: {result.stderr.strip() or result.stdout.strip()}")
|
|
40
39
|
return result.stdout.strip()
|
|
41
40
|
|
|
@@ -51,23 +50,24 @@ def shared(name):
|
|
|
51
50
|
|
|
52
51
|
def read_json(path):
|
|
53
52
|
require(stat.S_ISREG(path.lstat().st_mode), f"JSON must be a nonsymlink regular file: {path}")
|
|
54
|
-
return shared("archive_run")["loads_strict_json"](path.read_text())
|
|
53
|
+
return shared("archive_run")["loads_strict_json"](path.read_text(encoding="utf-8"))
|
|
55
54
|
|
|
56
55
|
|
|
57
56
|
def atomic_json(path, value):
|
|
58
57
|
fd, temporary = tempfile.mkstemp(dir=path.parent, prefix=".receipt-")
|
|
59
58
|
try:
|
|
60
|
-
with os.fdopen(fd, "w") as stream:
|
|
59
|
+
with os.fdopen(fd, "w", encoding="utf-8") as stream:
|
|
61
60
|
json.dump(value, stream, sort_keys=True, indent=2)
|
|
62
61
|
stream.write("\n")
|
|
63
62
|
stream.flush()
|
|
64
63
|
os.fsync(stream.fileno())
|
|
65
64
|
os.replace(temporary, path)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
if os.name != "nt":
|
|
66
|
+
directory = os.open(path.parent, os.O_RDONLY)
|
|
67
|
+
try:
|
|
68
|
+
os.fsync(directory)
|
|
69
|
+
finally:
|
|
70
|
+
os.close(directory)
|
|
71
71
|
finally:
|
|
72
72
|
Path(temporary).unlink(missing_ok=True)
|
|
73
73
|
|
|
@@ -167,7 +167,7 @@ def repository_from_url(url):
|
|
|
167
167
|
|
|
168
168
|
|
|
169
169
|
def policy(receipt, override):
|
|
170
|
-
config = subprocess.run(["git", "--git-dir", receipt["common_gitdir"], "config", "--local", "--get-all", "devlyn.completionMode"], capture_output=True, text=True)
|
|
170
|
+
config = subprocess.run(["git", "--git-dir", receipt["common_gitdir"], "config", "--local", "--get-all", "devlyn.completionMode"], capture_output=True, text=True, encoding="utf-8")
|
|
171
171
|
require(config.returncode in {0, 1}, "cannot read local completion policy: " + config.stderr.strip())
|
|
172
172
|
require(config.returncode == 1 or config.stdout in {"auto\n", "pr\n"}, "invalid local devlyn.completionMode; use auto|pr")
|
|
173
173
|
value = config.stdout.strip() if config.returncode == 0 else None
|
|
@@ -412,7 +412,7 @@ def stopped_writers(work, *, linked):
|
|
|
412
412
|
# The owner assertion covers its actual children; an OS observation catches
|
|
413
413
|
# other currently open files/cwds, not future writers or a universal lease.
|
|
414
414
|
if sys.platform == "darwin":
|
|
415
|
-
result = subprocess.run(["lsof", "-Fpn", "+D", str(work)], capture_output=True, text=True)
|
|
415
|
+
result = subprocess.run(["lsof", "-Fpn", "+D", str(work)], capture_output=True, text=True, encoding="utf-8")
|
|
416
416
|
require(result.returncode in {0, 1} and not result.stderr.strip(), "writer observation unavailable; retain tree and inspect writers")
|
|
417
417
|
pid = None
|
|
418
418
|
for line in result.stdout.splitlines():
|
|
@@ -508,8 +508,11 @@ def cleanup(receipt, path, pr, writers_stopped):
|
|
|
508
508
|
def locked_receipt(path):
|
|
509
509
|
require(path.name == "receipt.json" and path.parent.parent.name == "devlyn-completion" and path == path.resolve(), "receipt must be its original external nonsymlink path")
|
|
510
510
|
require(not (path.parent / "lock").is_symlink(), "receipt lock must not be a symlink")
|
|
511
|
-
with
|
|
512
|
-
|
|
511
|
+
with contextlib.ExitStack() as stack:
|
|
512
|
+
try:
|
|
513
|
+
stack.enter_context(shared("platform-support")["file_lock"](path.parent / "lock", blocking=True))
|
|
514
|
+
except (ImportError, OSError, AttributeError) as exc:
|
|
515
|
+
raise CompletionError(f"receipt lock unavailable: {path}: {exc}") from exc
|
|
513
516
|
receipt = read_json(path)
|
|
514
517
|
require(path == Path(receipt["common_gitdir"]) / "devlyn-completion" / receipt["id"] / "receipt.json", "receipt/common Gitdir binding mismatch")
|
|
515
518
|
require(receipt["id"] == hashlib.sha256(receipt["branch"].encode()).hexdigest()[:24] and receipt["branch"] not in {receipt["base"], "main", "master"}, "receipt branch ownership changed")
|
|
@@ -639,10 +642,10 @@ def self_test(names=None):
|
|
|
639
642
|
FAKE_GH = r'''#!/usr/bin/env python3
|
|
640
643
|
import json, os, pathlib, subprocess, sys
|
|
641
644
|
p = pathlib.Path(os.environ['FIXTURE_GH'])
|
|
642
|
-
d = json.loads(p.read_text()); a = sys.argv[1:]
|
|
643
|
-
def save(): p.write_text(json.dumps(d))
|
|
645
|
+
d = json.loads(p.read_text(encoding="utf-8")); a = sys.argv[1:]
|
|
646
|
+
def save(): p.write_text(json.dumps(d), encoding="utf-8")
|
|
644
647
|
def remote(ref):
|
|
645
|
-
r = subprocess.run([os.environ['REAL_GIT'], '--git-dir', d['bare'], 'rev-parse', '--verify', ref], capture_output=True, text=True)
|
|
648
|
+
r = subprocess.run([os.environ['REAL_GIT'], '--git-dir', d['bare'], 'rev-parse', '--verify', ref], capture_output=True, text=True, encoding="utf-8")
|
|
646
649
|
return r.stdout.strip() if r.returncode == 0 else None
|
|
647
650
|
if a[:2] == ['repo', 'view']:
|
|
648
651
|
assert a[2:] == ['github.com/test/project', '--json', 'nameWithOwner,url,defaultBranchRef,mergeCommitAllowed'], 'unsupported gh repo view arguments: '+str(a)
|
|
@@ -673,8 +676,8 @@ elif a[:2] == ['pr', 'merge']:
|
|
|
673
676
|
else:
|
|
674
677
|
g = [os.environ['REAL_GIT'],'--git-dir',d['bare']]
|
|
675
678
|
base = remote('refs/heads/main')
|
|
676
|
-
tree = subprocess.check_output(g+['rev-parse',sha+'^{tree}'],text=True).strip()
|
|
677
|
-
merge = subprocess.check_output(g+['commit-tree',tree,'-p',base,'-p',sha,'-m','merge fixture'],text=True).strip()
|
|
679
|
+
tree = subprocess.check_output(g+['rev-parse',sha+'^{tree}'],text=True, encoding="utf-8").strip()
|
|
680
|
+
merge = subprocess.check_output(g+['commit-tree',tree,'-p',base,'-p',sha,'-m','merge fixture'],text=True, encoding="utf-8").strip()
|
|
678
681
|
subprocess.check_call(g+['update-ref','refs/heads/main',merge,base])
|
|
679
682
|
d['pr'].update(state='MERGED',mergedAt='now',mergeCommit={'oid':merge})
|
|
680
683
|
save()
|
|
@@ -686,22 +689,22 @@ else:
|
|
|
686
689
|
GIT_WRAPPER = r'''#!/usr/bin/env python3
|
|
687
690
|
import json, os, pathlib, subprocess, sys
|
|
688
691
|
a = sys.argv[1:]
|
|
689
|
-
p = pathlib.Path(os.environ['FIXTURE_GH']); d = json.loads(p.read_text())
|
|
692
|
+
p = pathlib.Path(os.environ['FIXTURE_GH']); d = json.loads(p.read_text(encoding="utf-8"))
|
|
690
693
|
if 'push' in a and any(x.startswith('--force-with-lease=') for x in a) and d.pop('remote_delete_race',False):
|
|
691
694
|
subprocess.check_call([os.environ['REAL_GIT'],'--git-dir',d['bare'],'update-ref','refs/heads/'+d['pr']['headRefName'],d['race_sha']])
|
|
692
|
-
p.write_text(json.dumps(d))
|
|
695
|
+
p.write_text(json.dumps(d), encoding="utf-8")
|
|
693
696
|
for operation in ('push', 'fetch', 'ls-remote'):
|
|
694
697
|
if operation in a and 'origin' in a:
|
|
695
698
|
prefix = a[:a.index(operation)]
|
|
696
|
-
url = subprocess.check_output([os.environ['REAL_GIT'], *prefix, 'remote', 'get-url', *(['--push'] if operation == 'push' else []), '--all', 'origin'], text=True).strip()
|
|
699
|
+
url = subprocess.check_output([os.environ['REAL_GIT'], *prefix, 'remote', 'get-url', *(['--push'] if operation == 'push' else []), '--all', 'origin'], text=True, encoding="utf-8").strip()
|
|
697
700
|
if url in {'https://github.com/test/project.git', 'git@github.com:test/project.git', 'ssh://git@github.com/test/project.git'}:
|
|
698
701
|
a[a.index('origin')] = d['bare']
|
|
699
702
|
break
|
|
700
703
|
r = subprocess.run([os.environ['REAL_GIT'],*a])
|
|
701
704
|
event = 'delete' if 'push' in a and any(x.startswith(':refs/') for x in a) else 'push' if 'push' in a else 'remove' if 'worktree' in a and 'remove' in a else None
|
|
702
705
|
if r.returncode == 0 and event:
|
|
703
|
-
d = json.loads(p.read_text()); d[event+'s'] = d.get(event+'s',0)+1
|
|
704
|
-
fail = d.pop('interrupt_'+event,False); p.write_text(json.dumps(d))
|
|
706
|
+
d = json.loads(p.read_text(encoding="utf-8")); d[event+'s'] = d.get(event+'s',0)+1
|
|
707
|
+
fail = d.pop('interrupt_'+event,False); p.write_text(json.dumps(d), encoding="utf-8")
|
|
705
708
|
if fail: sys.exit(1)
|
|
706
709
|
sys.exit(r.returncode)
|
|
707
710
|
'''
|
|
@@ -717,7 +720,7 @@ class CompletionTests(unittest.TestCase):
|
|
|
717
720
|
self.bin = self.root / "bin"
|
|
718
721
|
self.bin.mkdir()
|
|
719
722
|
self.data = self.root / "gh.json"
|
|
720
|
-
self.data.write_text(json.dumps({"bare": str(self.bare)}))
|
|
723
|
+
self.data.write_text(json.dumps({"bare": str(self.bare)}), encoding="utf-8")
|
|
721
724
|
self.env = {**os.environ, "GIT_CONFIG_NOSYSTEM": "1", "GIT_CONFIG_GLOBAL": str(self.root / "gitconfig"),
|
|
722
725
|
"GIT_AUTHOR_NAME": "Fixture", "GIT_AUTHOR_EMAIL": "fixture@example.invalid",
|
|
723
726
|
"GIT_COMMITTER_NAME": "Fixture", "GIT_COMMITTER_EMAIL": "fixture@example.invalid",
|
|
@@ -728,13 +731,13 @@ class CompletionTests(unittest.TestCase):
|
|
|
728
731
|
del self.env[key]
|
|
729
732
|
for name, body in (("gh", FAKE_GH), ("git", GIT_WRAPPER)):
|
|
730
733
|
path = self.bin / name
|
|
731
|
-
path.write_text(body)
|
|
734
|
+
path.write_text(body, encoding="utf-8")
|
|
732
735
|
path.chmod(0o755)
|
|
733
736
|
self.env["PATH"] = str(self.bin) + os.pathsep + os.environ["PATH"]
|
|
734
737
|
self.run_cmd(["git", "init", "--bare", "--initial-branch=main", str(self.bare)])
|
|
735
738
|
self.run_cmd(["git", "init", "--initial-branch=main", str(self.work)])
|
|
736
|
-
(self.work / ".gitignore").write_text(".devlyn/\nignored/\n")
|
|
737
|
-
(self.work / "product").write_text("baseline\n")
|
|
739
|
+
(self.work / ".gitignore").write_text(".devlyn/\nignored/\n", encoding="utf-8")
|
|
740
|
+
(self.work / "product").write_text("baseline\n", encoding="utf-8")
|
|
738
741
|
self.g("add", ".")
|
|
739
742
|
self.g("commit", "-m", "baseline")
|
|
740
743
|
self.g("remote", "add", "origin", "https://github.com/test/project.git")
|
|
@@ -742,7 +745,7 @@ class CompletionTests(unittest.TestCase):
|
|
|
742
745
|
self.configure(pushs=0)
|
|
743
746
|
|
|
744
747
|
def run_cmd(self, args, *, cwd=None, success=True):
|
|
745
|
-
r = subprocess.run(args, cwd=cwd or self.root, env=self.env, capture_output=True, text=True)
|
|
748
|
+
r = subprocess.run(args, cwd=cwd or self.root, env=self.env, capture_output=True, text=True, encoding="utf-8")
|
|
746
749
|
if success:
|
|
747
750
|
self.assertEqual(r.returncode, 0, r.stderr + r.stdout)
|
|
748
751
|
return r
|
|
@@ -751,8 +754,8 @@ class CompletionTests(unittest.TestCase):
|
|
|
751
754
|
return self.run_cmd(["git", "-C", str(work or self.work), *args]).stdout.strip()
|
|
752
755
|
|
|
753
756
|
def configure(self, **values):
|
|
754
|
-
d = json.loads(self.data.read_text()); d.update(values)
|
|
755
|
-
self.data.write_text(json.dumps(d))
|
|
757
|
+
d = json.loads(self.data.read_text(encoding="utf-8")); d.update(values)
|
|
758
|
+
self.data.write_text(json.dumps(d), encoding="utf-8")
|
|
756
759
|
|
|
757
760
|
def cli(self, *args, success=True, cwd=None):
|
|
758
761
|
r = self.run_cmd([sys.executable, str(Path(__file__).resolve()), *map(str, args)], success=success, cwd=cwd)
|
|
@@ -769,47 +772,47 @@ class CompletionTests(unittest.TestCase):
|
|
|
769
772
|
|
|
770
773
|
def accept(self, pipeline=False, queue=False, spec_expected=None):
|
|
771
774
|
if spec_expected is not None:
|
|
772
|
-
(self.task / "spec.md").write_text("# Fixture\nProduct contains accepted bytes.\n")
|
|
773
|
-
(self.task / "spec.expected.json").write_text(json.dumps(spec_expected))
|
|
775
|
+
(self.task / "spec.md").write_text("# Fixture\nProduct contains accepted bytes.\n", encoding="utf-8")
|
|
776
|
+
(self.task / "spec.expected.json").write_text(json.dumps(spec_expected), encoding="utf-8")
|
|
774
777
|
self.g("add", "spec.md", "spec.expected.json", work=self.task)
|
|
775
|
-
(self.task / "product").write_text("accepted\n")
|
|
778
|
+
(self.task / "product").write_text("accepted\n", encoding="utf-8")
|
|
776
779
|
self.g("add", "product", work=self.task)
|
|
777
780
|
self.g("commit", "-m", "scoped task", work=self.task)
|
|
778
781
|
self.sha = self.g("rev-parse", "HEAD", work=self.task)
|
|
779
782
|
evidence = self.task / ".devlyn"
|
|
780
783
|
evidence.mkdir(exist_ok=True)
|
|
781
|
-
(evidence / "checks.txt").write_text("actual fixture check: accepted bytes\n")
|
|
784
|
+
(evidence / "checks.txt").write_text("actual fixture check: accepted bytes\n", encoding="utf-8")
|
|
782
785
|
a = {"kind": "direct", "task": "fixture", "source_sha": self.sha,
|
|
783
786
|
"checks": [{"command": "fixture byte assertion", "evidence": ".devlyn/checks.txt"}]}
|
|
784
|
-
self.assertEqual((self.task / "product").read_text(), "accepted\n")
|
|
787
|
+
self.assertEqual((self.task / "product").read_text(encoding="utf-8"), "accepted\n")
|
|
785
788
|
if pipeline:
|
|
786
789
|
a = {"kind": "pipeline", "task": "fixture", "source_sha": self.sha, "run_id": "fixture-run"}
|
|
787
790
|
archive = evidence / "runs" / a["run_id"]
|
|
788
791
|
archive.mkdir(parents=True)
|
|
789
792
|
self.archive = archive
|
|
790
793
|
report = "<!-- devlyn:final-report run_id=fixture-run -->\nFixture completed.\n"
|
|
791
|
-
(archive / "final-report.md").write_text(report)
|
|
794
|
+
(archive / "final-report.md").write_text(report, encoding="utf-8")
|
|
792
795
|
phases = {n: {"started_at": "2026-09-10T00:00:00Z", "completed_at": "2026-09-10T00:00:01Z", "verdict": "PASS"}
|
|
793
796
|
for n in ("plan", "implement", "build_gate", "cleanup", "verify", "final_report")}
|
|
794
797
|
phases["cleanup"]["post_sha"] = self.sha
|
|
795
798
|
phases["final_report"].update(output_sha256=hashlib.sha256(report.encode()).hexdigest(), artifacts={"log_file": ".devlyn/final-report.md"})
|
|
796
799
|
criteria = "# Fixture acceptance\nProduct contains accepted bytes.\n"
|
|
797
|
-
(archive / "criteria.generated.md").write_text(criteria)
|
|
800
|
+
(archive / "criteria.generated.md").write_text(criteria, encoding="utf-8")
|
|
798
801
|
source = {"type": "generated", "criteria_path": ".devlyn/criteria.generated.md", "criteria_sha256": hashlib.sha256(criteria.encode()).hexdigest()}
|
|
799
802
|
self.state = {"run_id": a["run_id"], "mode": "free-form", "source": source, "phases": phases, "process_evidence": None}
|
|
800
803
|
if spec_expected is not None:
|
|
801
804
|
self.state["mode"] = "spec"
|
|
802
805
|
self.state["source"] = {"type": "spec", "spec_path": "spec.md", "spec_sha256": hashlib.sha256((self.task / "spec.md").read_bytes()).hexdigest()}
|
|
803
|
-
(archive / "pipeline.state.json").write_text(json.dumps(self.state))
|
|
804
|
-
(archive / "finish-gate.summary.json").write_text(json.dumps({"mode": self.state["mode"], "exit": 0, "offenders": 0, "checked": 1}))
|
|
805
|
-
(archive / "verify-merge.summary.json").write_text(json.dumps({"verdict": "PASS"}))
|
|
806
|
+
(archive / "pipeline.state.json").write_text(json.dumps(self.state), encoding="utf-8")
|
|
807
|
+
(archive / "finish-gate.summary.json").write_text(json.dumps({"mode": self.state["mode"], "exit": 0, "offenders": 0, "checked": 1}), encoding="utf-8")
|
|
808
|
+
(archive / "verify-merge.summary.json").write_text(json.dumps({"verdict": "PASS"}), encoding="utf-8")
|
|
806
809
|
if queue:
|
|
807
|
-
(self.task / "queue.md").write_text("- [x] fixture\n")
|
|
810
|
+
(self.task / "queue.md").write_text("- [x] fixture\n", encoding="utf-8")
|
|
808
811
|
self.g("add", "queue.md", work=self.task)
|
|
809
812
|
self.g("commit", "-m", "terminal queue", work=self.task)
|
|
810
813
|
a["queue"] = {"commit": self.g("rev-parse", "HEAD", work=self.task), "file": "queue.md"}
|
|
811
814
|
self.acceptance = evidence / "acceptance.json"
|
|
812
|
-
self.acceptance.write_text(json.dumps(a))
|
|
815
|
+
self.acceptance.write_text(json.dumps(a), encoding="utf-8")
|
|
813
816
|
|
|
814
817
|
def complete(self, *args, success=True, cwd=None, acceptance=True):
|
|
815
818
|
command_args = ["complete", "--receipt", self.receipt]
|
|
@@ -843,11 +846,11 @@ class CompletionTests(unittest.TestCase):
|
|
|
843
846
|
alias = "git@github.com:test/project.git"
|
|
844
847
|
self.g("config", "url." + alias + ".insteadOf", intended)
|
|
845
848
|
self.allocate(); self.accept()
|
|
846
|
-
self.assertEqual(json.loads(self.receipt.read_text())["remote_url"], {"literal": intended, "fetch": alias, "push": alias})
|
|
849
|
+
self.assertEqual(json.loads(self.receipt.read_text(encoding="utf-8"))["remote_url"], {"literal": intended, "fetch": alias, "push": alias})
|
|
847
850
|
result, _ = self.complete("--mode", "pr")
|
|
848
851
|
self.assertEqual(result["status"], "PR")
|
|
849
852
|
self.assertEqual(self.g("ls-remote", "origin", "refs/heads/task/fixture").split()[0], self.sha)
|
|
850
|
-
self.assertEqual(json.loads(self.data.read_text())["pushs"], 1)
|
|
853
|
+
self.assertEqual(json.loads(self.data.read_text(encoding="utf-8"))["pushs"], 1)
|
|
851
854
|
|
|
852
855
|
def test_remote_rewrite_after_allocation_is_rejected(self):
|
|
853
856
|
self.allocate(); self.accept()
|
|
@@ -868,9 +871,9 @@ class CompletionTests(unittest.TestCase):
|
|
|
868
871
|
destination = "https://github.com/test/other.git"
|
|
869
872
|
self.g("config", "url." + destination + ".insteadOf", "https://github.com/test/project.git")
|
|
870
873
|
# Reproduce a receipt the old literal-only allocation check accepted.
|
|
871
|
-
receipt = json.loads(self.receipt.read_text())
|
|
874
|
+
receipt = json.loads(self.receipt.read_text(encoding="utf-8"))
|
|
872
875
|
receipt["remote_url"].update(fetch=destination, push=destination)
|
|
873
|
-
self.receipt.write_text(json.dumps(receipt))
|
|
876
|
+
self.receipt.write_text(json.dumps(receipt), encoding="utf-8")
|
|
874
877
|
local_refs = self.g("show-ref")
|
|
875
878
|
remote_refs = self.run_cmd(["git", "--git-dir", str(self.bare), "show-ref"]).stdout
|
|
876
879
|
server = self.data.read_bytes()
|
|
@@ -891,17 +894,17 @@ class CompletionTests(unittest.TestCase):
|
|
|
891
894
|
for _ in range(2):
|
|
892
895
|
result, _ = self.complete("--mode", "auto")
|
|
893
896
|
self.assertEqual(result["status"], "PENDING")
|
|
894
|
-
d = json.loads(self.data.read_text())
|
|
897
|
+
d = json.loads(self.data.read_text(encoding="utf-8"))
|
|
895
898
|
self.assertEqual((d["creates"], d["merges"], d["pushs"]), (1, 1, 1))
|
|
896
899
|
|
|
897
900
|
def test_in_place_retains_ignored_data(self):
|
|
898
901
|
self.allocate(); self.accept()
|
|
899
902
|
(self.task / "ignored").mkdir()
|
|
900
|
-
(self.task / "ignored" / "user.txt").write_text("retain me")
|
|
903
|
+
(self.task / "ignored" / "user.txt").write_text("retain me", encoding="utf-8")
|
|
901
904
|
result, _ = self.complete("--writers-stopped")
|
|
902
905
|
self.assertEqual(result["status"], "COMPLETE")
|
|
903
906
|
self.assertEqual(self.g("branch", "--show-current"), "main")
|
|
904
|
-
self.assertEqual((self.task / "ignored/user.txt").read_text(), "retain me")
|
|
907
|
+
self.assertEqual((self.task / "ignored/user.txt").read_text(encoding="utf-8"), "retain me")
|
|
905
908
|
self.assertEqual(self.g("branch", "--list", "task/fixture"), "")
|
|
906
909
|
self.assertEqual(self.g("ls-remote", "origin", "refs/heads/task/fixture"), "")
|
|
907
910
|
again, _ = self.complete("--writers-stopped", acceptance=False)
|
|
@@ -915,7 +918,7 @@ class CompletionTests(unittest.TestCase):
|
|
|
915
918
|
result, r = self.complete("--writers-stopped", success=False)
|
|
916
919
|
self.assertNotEqual(r.returncode, 0)
|
|
917
920
|
self.assertFalse(self.task.exists())
|
|
918
|
-
saved = json.loads(self.receipt.read_text())
|
|
921
|
+
saved = json.loads(self.receipt.read_text(encoding="utf-8"))
|
|
919
922
|
recovered = self.receipt.parent / "custody" / ".devlyn/runs/fixture-run/final-report.md"
|
|
920
923
|
self.assertEqual(hashlib.sha256(recovered.read_bytes()).hexdigest(), saved["files"][".devlyn/runs/fixture-run/final-report.md"]["sha256"])
|
|
921
924
|
result, _ = self.complete("--writers-stopped", acceptance=False)
|
|
@@ -933,30 +936,30 @@ class CompletionTests(unittest.TestCase):
|
|
|
933
936
|
if case == "unfinished": state["phases"]["verify"]["completed_at"] = None
|
|
934
937
|
if case == "digest": state["phases"]["final_report"]["output_sha256"] = "0"*64
|
|
935
938
|
if case == "source": state["phases"]["cleanup"]["post_sha"] = self.g("rev-parse", "main")
|
|
936
|
-
(self.archive / "pipeline.state.json").write_text(json.dumps(state))
|
|
939
|
+
(self.archive / "pipeline.state.json").write_text(json.dumps(state), encoding="utf-8")
|
|
937
940
|
with self.subTest(case=case):
|
|
938
941
|
_, r = self.complete(success=False)
|
|
939
942
|
self.assertNotEqual(r.returncode, 0)
|
|
940
943
|
self.assertEqual(self.g("ls-remote", "origin", "refs/heads/task/fixture"), "")
|
|
941
|
-
(self.archive / "pipeline.state.json").write_text(json.dumps(original))
|
|
944
|
+
(self.archive / "pipeline.state.json").write_text(json.dumps(original), encoding="utf-8")
|
|
942
945
|
for value in ("typo", "", " auto"):
|
|
943
946
|
self.g("config", "--local", "devlyn.completionMode", value)
|
|
944
947
|
_, r = self.complete("--mode", "pr", success=False)
|
|
945
948
|
self.assertNotEqual(r.returncode, 0)
|
|
946
949
|
result, _ = self.complete("--local-only")
|
|
947
950
|
self.assertEqual(result["status"], "LOCAL_ONLY")
|
|
948
|
-
self.assertEqual(json.loads(self.data.read_text()).get("creates", 0), 0)
|
|
951
|
+
self.assertEqual(json.loads(self.data.read_text(encoding="utf-8")).get("creates", 0), 0)
|
|
949
952
|
|
|
950
953
|
def test_queue_only_and_unverified_descendant(self):
|
|
951
954
|
self.allocate(); self.accept(pipeline=True, queue=True)
|
|
952
955
|
result, _ = self.complete("--mode", "pr")
|
|
953
956
|
self.assertEqual(result["status"], "PR")
|
|
954
|
-
(self.task / "product").write_text("unverified")
|
|
957
|
+
(self.task / "product").write_text("unverified", encoding="utf-8")
|
|
955
958
|
self.g("add", "product", work=self.task)
|
|
956
959
|
self.g("commit", "-m", "unverified", work=self.task)
|
|
957
960
|
_, r = self.complete(success=False)
|
|
958
961
|
self.assertNotEqual(r.returncode, 0)
|
|
959
|
-
self.assertEqual(json.loads(self.data.read_text()).get("merges", 0), 0)
|
|
962
|
+
self.assertEqual(json.loads(self.data.read_text(encoding="utf-8")).get("merges", 0), 0)
|
|
960
963
|
|
|
961
964
|
def test_interrupted_external_effects(self):
|
|
962
965
|
self.allocate(); self.accept()
|
|
@@ -966,7 +969,7 @@ class CompletionTests(unittest.TestCase):
|
|
|
966
969
|
self.assertNotEqual(r.returncode, 0)
|
|
967
970
|
result, _ = self.complete("--writers-stopped")
|
|
968
971
|
self.assertEqual(result["status"], "COMPLETE")
|
|
969
|
-
d = json.loads(self.data.read_text())
|
|
972
|
+
d = json.loads(self.data.read_text(encoding="utf-8"))
|
|
970
973
|
self.assertEqual((d["pushs"], d["creates"], d["merges"]), (1, 1, 1))
|
|
971
974
|
|
|
972
975
|
def test_retains_unknown_dirty_locked_and_active_tree(self):
|
|
@@ -974,7 +977,7 @@ class CompletionTests(unittest.TestCase):
|
|
|
974
977
|
for case in ("ignored", "dirty", "untracked", "locked", "cwd", "no-yield"):
|
|
975
978
|
path = self.task / ("ignored/unknown" if case == "ignored" else "product" if case == "dirty" else "unknown")
|
|
976
979
|
if case in {"ignored", "dirty", "untracked"}:
|
|
977
|
-
path.parent.mkdir(exist_ok=True); path.write_text("do not delete")
|
|
980
|
+
path.parent.mkdir(exist_ok=True); path.write_text("do not delete", encoding="utf-8")
|
|
978
981
|
if case == "locked": self.g("worktree", "lock", str(self.task))
|
|
979
982
|
with self.subTest(case=case):
|
|
980
983
|
_, r = self.complete(*([] if case == "no-yield" else ["--writers-stopped"]), cwd=self.task if case == "cwd" else None, success=False)
|
|
@@ -982,7 +985,7 @@ class CompletionTests(unittest.TestCase):
|
|
|
982
985
|
self.assertTrue(self.task.exists())
|
|
983
986
|
self.assertNotEqual(self.g("branch", "--list", "task/fixture"), "")
|
|
984
987
|
if case in {"ignored", "untracked"}: path.unlink()
|
|
985
|
-
if case == "dirty": path.write_text("accepted\n")
|
|
988
|
+
if case == "dirty": path.write_text("accepted\n", encoding="utf-8")
|
|
986
989
|
if case == "locked": self.g("worktree", "unlock", str(self.task))
|
|
987
990
|
|
|
988
991
|
def test_no_adoption_and_remote_head_race(self):
|
|
@@ -993,34 +996,34 @@ class CompletionTests(unittest.TestCase):
|
|
|
993
996
|
self.run_cmd(["git", "--git-dir", str(self.bare), "update-ref", "refs/heads/task/fixture", self.g("rev-parse", "main")])
|
|
994
997
|
_, r = self.complete(success=False)
|
|
995
998
|
self.assertNotEqual(r.returncode, 0)
|
|
996
|
-
self.assertEqual(json.loads(self.data.read_text()).get("merges",0), 0)
|
|
999
|
+
self.assertEqual(json.loads(self.data.read_text(encoding="utf-8")).get("merges",0), 0)
|
|
997
1000
|
|
|
998
1001
|
def test_concurrent_completions(self):
|
|
999
1002
|
self.allocate(); self.accept(); self.configure(pending=True)
|
|
1000
1003
|
args = [sys.executable, str(Path(__file__).resolve()), "complete", "--receipt", str(self.receipt), "--acceptance", str(self.acceptance)]
|
|
1001
|
-
procs = [subprocess.Popen(args, cwd=self.root, env=self.env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) for _ in range(2)]
|
|
1004
|
+
procs = [subprocess.Popen(args, cwd=self.root, env=self.env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8") for _ in range(2)]
|
|
1002
1005
|
for proc in procs:
|
|
1003
1006
|
out, err = proc.communicate(timeout=30)
|
|
1004
1007
|
self.assertEqual(proc.returncode, 0, out+err)
|
|
1005
|
-
d = json.loads(self.data.read_text())
|
|
1008
|
+
d = json.loads(self.data.read_text(encoding="utf-8"))
|
|
1006
1009
|
self.assertEqual((d["pushs"], d["creates"], d["merges"]), (1,1,1))
|
|
1007
1010
|
|
|
1008
1011
|
def test_custody_failure_and_evidence_tamper(self):
|
|
1009
1012
|
self.allocate(linked=True); self.accept()
|
|
1010
1013
|
bad = self.receipt.parent / "custody/.devlyn/checks.txt"
|
|
1011
1014
|
bad.parent.mkdir(parents=True)
|
|
1012
|
-
bad.write_text("partial interrupted copy")
|
|
1015
|
+
bad.write_text("partial interrupted copy", encoding="utf-8")
|
|
1013
1016
|
_, r = self.complete("--writers-stopped", success=False)
|
|
1014
1017
|
self.assertNotEqual(r.returncode, 0)
|
|
1015
1018
|
self.assertTrue(self.task.exists())
|
|
1016
|
-
self.assertEqual((self.task / ".devlyn/checks.txt").read_text(), "actual fixture check: accepted bytes\n")
|
|
1019
|
+
self.assertEqual((self.task / ".devlyn/checks.txt").read_text(encoding="utf-8"), "actual fixture check: accepted bytes\n")
|
|
1017
1020
|
self.assertEqual(self.g("ls-remote", "origin", "refs/heads/task/fixture"), "")
|
|
1018
1021
|
bad.unlink() # Owner repairs only the identified corrupt fixture copy.
|
|
1019
1022
|
self.complete("--mode", "pr")
|
|
1020
|
-
(self.task / ".devlyn/checks.txt").write_text("altered after acceptance")
|
|
1023
|
+
(self.task / ".devlyn/checks.txt").write_text("altered after acceptance", encoding="utf-8")
|
|
1021
1024
|
_, r = self.complete("--mode", "auto", success=False)
|
|
1022
1025
|
self.assertNotEqual(r.returncode, 0)
|
|
1023
|
-
self.assertEqual(json.loads(self.data.read_text()).get("merges",0), 0)
|
|
1026
|
+
self.assertEqual(json.loads(self.data.read_text(encoding="utf-8")).get("merges",0), 0)
|
|
1024
1027
|
|
|
1025
1028
|
def test_actual_foreign_writer_and_registration(self):
|
|
1026
1029
|
self.allocate(linked=True); self.accept()
|
|
@@ -1053,7 +1056,7 @@ class CompletionTests(unittest.TestCase):
|
|
|
1053
1056
|
self.configure(merge_allowed=False)
|
|
1054
1057
|
_, r = self.complete(success=False)
|
|
1055
1058
|
self.assertNotEqual(r.returncode, 0)
|
|
1056
|
-
self.assertEqual(json.loads(self.data.read_text()).get("pushs",0), 0)
|
|
1059
|
+
self.assertEqual(json.loads(self.data.read_text(encoding="utf-8")).get("pushs",0), 0)
|
|
1057
1060
|
self.complete("--mode", "pr")
|
|
1058
1061
|
result, _ = self.complete()
|
|
1059
1062
|
self.assertEqual(result["status"], "PR")
|
|
@@ -1068,15 +1071,15 @@ class CompletionTests(unittest.TestCase):
|
|
|
1068
1071
|
|
|
1069
1072
|
def test_queue_commit_cannot_hide_product_changes(self):
|
|
1070
1073
|
self.allocate(); self.accept(pipeline=True, queue=True)
|
|
1071
|
-
(self.task / "product").write_text("unverified extra\n")
|
|
1074
|
+
(self.task / "product").write_text("unverified extra\n", encoding="utf-8")
|
|
1072
1075
|
self.g("add", "product", work=self.task)
|
|
1073
1076
|
self.g("commit", "--amend", "--no-edit", work=self.task)
|
|
1074
|
-
a = json.loads(self.acceptance.read_text())
|
|
1077
|
+
a = json.loads(self.acceptance.read_text(encoding="utf-8"))
|
|
1075
1078
|
a["queue"]["commit"] = self.g("rev-parse", "HEAD", work=self.task)
|
|
1076
|
-
self.acceptance.write_text(json.dumps(a))
|
|
1079
|
+
self.acceptance.write_text(json.dumps(a), encoding="utf-8")
|
|
1077
1080
|
_, r = self.complete(success=False)
|
|
1078
1081
|
self.assertNotEqual(r.returncode, 0)
|
|
1079
|
-
self.assertEqual(json.loads(self.data.read_text()).get("pushs",0), 0)
|
|
1082
|
+
self.assertEqual(json.loads(self.data.read_text(encoding="utf-8")).get("pushs",0), 0)
|
|
1080
1083
|
|
|
1081
1084
|
def test_pipeline_sealed_process_evidence(self):
|
|
1082
1085
|
self.allocate(); self.accept(pipeline=True)
|
|
@@ -1088,15 +1091,15 @@ class CompletionTests(unittest.TestCase):
|
|
|
1088
1091
|
results = {"commands": evidence_module["bound_carrier_summary_commands"](self.task, carrier), "process_evidence": carrier}
|
|
1089
1092
|
self.state["phases"]["verify"]["round"] = 0
|
|
1090
1093
|
self.state["process_evidence"] = [carrier]
|
|
1091
|
-
(self.archive / "pipeline.state.json").write_text(json.dumps(self.state))
|
|
1092
|
-
(self.archive / "spec-verify.results.json").write_text(json.dumps(results))
|
|
1094
|
+
(self.archive / "pipeline.state.json").write_text(json.dumps(self.state), encoding="utf-8")
|
|
1095
|
+
(self.archive / "spec-verify.results.json").write_text(json.dumps(results), encoding="utf-8")
|
|
1093
1096
|
shutil.move(str(self.task / ".devlyn/process-evidence"), str(self.archive / "process-evidence"))
|
|
1094
1097
|
stdout = self.archive / carrier["streams"][0]["stdout"]["path"].removeprefix(".devlyn/")
|
|
1095
1098
|
before = stdout.read_bytes()
|
|
1096
|
-
stdout.write_text("tampered")
|
|
1099
|
+
stdout.write_text("tampered", encoding="utf-8")
|
|
1097
1100
|
_, r = self.complete(success=False)
|
|
1098
1101
|
self.assertNotEqual(r.returncode, 0)
|
|
1099
|
-
self.assertEqual(json.loads(self.data.read_text()).get("pushs",0), 0)
|
|
1102
|
+
self.assertEqual(json.loads(self.data.read_text(encoding="utf-8")).get("pushs",0), 0)
|
|
1100
1103
|
stdout.write_bytes(before)
|
|
1101
1104
|
result, _ = self.complete("--mode", "pr")
|
|
1102
1105
|
self.assertEqual(result["status"], "PR")
|
|
@@ -1105,7 +1108,7 @@ class CompletionTests(unittest.TestCase):
|
|
|
1105
1108
|
self.allocate(); self.accept()
|
|
1106
1109
|
user_file = self.task / "ignored/user.txt"
|
|
1107
1110
|
user_file.parent.mkdir()
|
|
1108
|
-
user_file.write_text("retained user data")
|
|
1111
|
+
user_file.write_text("retained user data", encoding="utf-8")
|
|
1109
1112
|
self.configure(interrupt_delete=True)
|
|
1110
1113
|
_, r = self.complete("--writers-stopped", success=False)
|
|
1111
1114
|
self.assertNotEqual(r.returncode, 0)
|
|
@@ -1113,13 +1116,13 @@ class CompletionTests(unittest.TestCase):
|
|
|
1113
1116
|
other = self.root / "advance"
|
|
1114
1117
|
self.run_cmd(["git", "clone", str(self.bare), str(other)])
|
|
1115
1118
|
(other / "ignored").mkdir()
|
|
1116
|
-
(other / "ignored/user.txt").write_text("new upstream tracked file")
|
|
1119
|
+
(other / "ignored/user.txt").write_text("new upstream tracked file", encoding="utf-8")
|
|
1117
1120
|
self.g("add", "-f", "ignored/user.txt", work=other)
|
|
1118
1121
|
self.g("commit", "-m", "upstream collision", work=other)
|
|
1119
1122
|
self.g("push", "origin", "main", work=other)
|
|
1120
1123
|
_, r = self.complete("--writers-stopped", acceptance=False, success=False)
|
|
1121
1124
|
self.assertNotEqual(r.returncode, 0)
|
|
1122
|
-
self.assertEqual(user_file.read_text(), "retained user data")
|
|
1125
|
+
self.assertEqual(user_file.read_text(encoding="utf-8"), "retained user data")
|
|
1123
1126
|
self.assertNotEqual(self.g("branch", "--list", "task/fixture"), "")
|
|
1124
1127
|
|
|
1125
1128
|
def test_replaced_tree_and_unsafe_custody(self):
|
|
@@ -1138,21 +1141,21 @@ class CompletionTests(unittest.TestCase):
|
|
|
1138
1141
|
self.assertNotEqual(r.returncode, 0)
|
|
1139
1142
|
self.assertIn("replaced", json.loads(r.stdout)["reason"])
|
|
1140
1143
|
self.assertTrue(self.task.exists())
|
|
1141
|
-
self.assertEqual(json.loads(self.data.read_text()).get("pushs",0), 0)
|
|
1144
|
+
self.assertEqual(json.loads(self.data.read_text(encoding="utf-8")).get("pushs",0), 0)
|
|
1142
1145
|
|
|
1143
1146
|
def test_spec_source_and_report_binding(self):
|
|
1144
1147
|
self.allocate(); self.accept(pipeline=True, spec_expected={"verification_commands": []})
|
|
1145
1148
|
report = self.archive / "final-report.md"
|
|
1146
1149
|
original = report.read_bytes()
|
|
1147
|
-
report.write_text("<!-- devlyn:final-report run_id=another-run -->\nBody\n")
|
|
1150
|
+
report.write_text("<!-- devlyn:final-report run_id=another-run -->\nBody\n", encoding="utf-8")
|
|
1148
1151
|
self.state["phases"]["final_report"]["output_sha256"] = hashlib.sha256(report.read_bytes()).hexdigest()
|
|
1149
|
-
(self.archive / "pipeline.state.json").write_text(json.dumps(self.state))
|
|
1152
|
+
(self.archive / "pipeline.state.json").write_text(json.dumps(self.state), encoding="utf-8")
|
|
1150
1153
|
_, r = self.complete(success=False)
|
|
1151
1154
|
self.assertNotEqual(r.returncode, 0)
|
|
1152
|
-
self.assertEqual(json.loads(self.data.read_text()).get("pushs",0), 0)
|
|
1155
|
+
self.assertEqual(json.loads(self.data.read_text(encoding="utf-8")).get("pushs",0), 0)
|
|
1153
1156
|
report.write_bytes(original)
|
|
1154
1157
|
self.state["phases"]["final_report"]["output_sha256"] = hashlib.sha256(original).hexdigest()
|
|
1155
|
-
(self.archive / "pipeline.state.json").write_text(json.dumps(self.state))
|
|
1158
|
+
(self.archive / "pipeline.state.json").write_text(json.dumps(self.state), encoding="utf-8")
|
|
1156
1159
|
result, _ = self.complete("--mode", "pr")
|
|
1157
1160
|
self.assertEqual(result["status"], "PR")
|
|
1158
1161
|
|
|
@@ -1162,8 +1165,9 @@ class CompletionTests(unittest.TestCase):
|
|
|
1162
1165
|
_, r = self.complete(success=False)
|
|
1163
1166
|
self.assertNotEqual(r.returncode, 0)
|
|
1164
1167
|
self.assertIn("implement process evidence", json.loads(r.stdout)["reason"])
|
|
1165
|
-
self.assertEqual(json.loads(self.data.read_text()).get("pushs",0), 0)
|
|
1168
|
+
self.assertEqual(json.loads(self.data.read_text(encoding="utf-8")).get("pushs",0), 0)
|
|
1166
1169
|
|
|
1167
1170
|
|
|
1168
1171
|
if __name__ == "__main__":
|
|
1172
|
+
runpy.run_path(str(Path(__file__).with_name("platform-support.py")))["configure_utf8"]()
|
|
1169
1173
|
sys.exit(main())
|
|
@@ -474,7 +474,7 @@ def read_findings(devlyn: pathlib.Path) -> tuple[list[dict[str, Any]], dict[str,
|
|
|
474
474
|
)
|
|
475
475
|
if (devlyn / "pipeline.state.json").is_file():
|
|
476
476
|
try:
|
|
477
|
-
state = loads_strict_json((devlyn / "pipeline.state.json").read_text())
|
|
477
|
+
state = loads_strict_json((devlyn / "pipeline.state.json").read_text(encoding="utf-8"))
|
|
478
478
|
required = JUDGE_ROLE_EVIDENCE["required_roles"](state)
|
|
479
479
|
if rank(source_verdicts.get("mechanical")) >= 2:
|
|
480
480
|
required = []
|
|
@@ -1663,21 +1663,21 @@ def self_test() -> int:
|
|
|
1663
1663
|
for malformed in (None, {}, {"roles": {}}):
|
|
1664
1664
|
bad_state = loads_strict_json(original_state)
|
|
1665
1665
|
bad_state["role_resolution"] = malformed
|
|
1666
|
-
(devlyn / "pipeline.state.json").write_text(json.dumps(bad_state))
|
|
1666
|
+
(devlyn / "pipeline.state.json").write_text(json.dumps(bad_state), encoding="utf-8")
|
|
1667
1667
|
result = subprocess.run([sys.executable, str(pathlib.Path(__file__).resolve()),
|
|
1668
|
-
"--devlyn-dir", str(devlyn), "--write-state"], capture_output=True, text=True)
|
|
1668
|
+
"--devlyn-dir", str(devlyn), "--write-state"], capture_output=True, text=True, encoding="utf-8")
|
|
1669
1669
|
assert result.returncode == 0 and "Traceback" not in result.stderr, result.stderr
|
|
1670
|
-
persisted = loads_strict_json((devlyn / "pipeline.state.json").read_text())
|
|
1670
|
+
persisted = loads_strict_json((devlyn / "pipeline.state.json").read_text(encoding="utf-8"))
|
|
1671
1671
|
assert persisted["phases"]["verify"]["verdict"] == "BLOCKED"
|
|
1672
|
-
assert "verify-role-resolution-invalid" in (devlyn / "verify-merged.findings.jsonl").read_text()
|
|
1672
|
+
assert "verify-role-resolution-invalid" in (devlyn / "verify-merged.findings.jsonl").read_text(encoding="utf-8")
|
|
1673
1673
|
(devlyn / "pipeline.state.json").write_bytes(original_state)
|
|
1674
1674
|
different_primary = {"engine": "codex", "phases": {"verify": {"engine": "claude"}}}
|
|
1675
|
-
(devlyn / "pipeline.state.json").write_text(json.dumps(different_primary))
|
|
1676
|
-
(devlyn / "verify.primary.timeout.json").write_text(json.dumps({"engine": "claude", "budget_seconds": 600}))
|
|
1675
|
+
(devlyn / "pipeline.state.json").write_text(json.dumps(different_primary), encoding="utf-8")
|
|
1676
|
+
(devlyn / "verify.primary.timeout.json").write_text(json.dumps({"engine": "claude", "budget_seconds": 600}), encoding="utf-8")
|
|
1677
1677
|
marker, violation = read_primary_timeout_marker(devlyn)
|
|
1678
1678
|
assert marker is not None and violation is None
|
|
1679
1679
|
different_primary["phases"]["verify"]["engine"] = None
|
|
1680
|
-
(devlyn / "pipeline.state.json").write_text(json.dumps(different_primary))
|
|
1680
|
+
(devlyn / "pipeline.state.json").write_text(json.dumps(different_primary), encoding="utf-8")
|
|
1681
1681
|
assert read_primary_timeout_marker(devlyn)[1] is not None
|
|
1682
1682
|
(devlyn / "verify.primary.timeout.json").unlink()
|
|
1683
1683
|
(devlyn / "pipeline.state.json").write_bytes(original_state)
|
|
@@ -3433,4 +3433,5 @@ def main() -> int:
|
|
|
3433
3433
|
|
|
3434
3434
|
|
|
3435
3435
|
if __name__ == "__main__":
|
|
3436
|
+
runpy.run_path(str(pathlib.Path(__file__).with_name("platform-support.py")))["configure_utf8"]()
|
|
3436
3437
|
raise SystemExit(main())
|