devlyn-cli 3.1.0 → 3.1.2

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/README.md +6 -1
  2. package/bin/devlyn.js +69 -23
  3. package/config/skills/_shared/adapters/claude.md +3 -2
  4. package/config/skills/_shared/adapters/codex.md +1 -1
  5. package/config/skills/_shared/archive_run.py +5 -3
  6. package/config/skills/_shared/codex-config.md +13 -8
  7. package/config/skills/_shared/codex-monitored.sh +12 -19
  8. package/config/skills/_shared/collect-codex-findings.py +1 -0
  9. package/config/skills/_shared/engine-preflight.md +2 -0
  10. package/config/skills/_shared/finish-gate.py +4 -1
  11. package/config/skills/_shared/invocation-receipt.py +194 -5
  12. package/config/skills/_shared/judge-role-evidence.py +33 -6
  13. package/config/skills/_shared/phase-prompt-render.py +2 -0
  14. package/config/skills/_shared/platform-support.py +370 -0
  15. package/config/skills/_shared/process-evidence.py +20 -3
  16. package/config/skills/_shared/resolve-bootstrap.py +131 -63
  17. package/config/skills/_shared/resolve-stop-hook.py +3 -1
  18. package/config/skills/_shared/role-config.py +7 -5
  19. package/config/skills/_shared/run-bounded.py +35 -21
  20. package/config/skills/_shared/spec-verify-check.py +393 -167
  21. package/config/skills/_shared/state-phase-write.py +26 -9
  22. package/config/skills/_shared/task-complete.py +337 -97
  23. package/config/skills/_shared/verify-merge-findings.py +9 -8
  24. package/config/skills/devlyn:ideate/references/spec-template.md +1 -1
  25. package/config/skills/devlyn:resolve/SKILL.md +10 -7
  26. package/config/skills/devlyn:resolve/references/free-form-mode.md +1 -1
  27. package/config/skills/devlyn:resolve/references/phases/build-gate.md +1 -1
  28. package/config/skills/devlyn:resolve/references/phases/verify.md +14 -10
  29. package/config/skills/devlyn:resolve/references/state-schema.md +1 -1
  30. package/config/skills/devlyn:resolve/references/task-completion.md +28 -1
  31. package/package.json +1 -1
  32. package/scripts/lint-skills.sh +6 -5
@@ -2035,7 +2035,7 @@ def final_report_self_test() -> None:
2035
2035
  run_id = "rs-final-report-0126"
2036
2036
  write_state(state_path, {"version": "3.0", "run_id": run_id, "engine": "claude", "phases": {}})
2037
2037
  command = [sys.executable, str(script), "--devlyn-dir", ".devlyn", "--phase", "final_report"]
2038
- spawned = subprocess.run(command + ["spawn", "--round", "0"], cwd=work, capture_output=True, text=True)
2038
+ spawned = subprocess.run(command + ["spawn", "--round", "0"], cwd=work, capture_output=True, text=True, encoding="utf-8")
2039
2039
  assert spawned.returncode == 0, spawned.stderr
2040
2040
  before = state_path.read_bytes()
2041
2041
  marker = f"<!-- devlyn:final-report run_id={run_id} -->\n"
@@ -2074,12 +2074,12 @@ def final_report_self_test() -> None:
2074
2074
  report.write_bytes(valid)
2075
2075
  args = ["--log-file", str(outside)]
2076
2076
  result = subprocess.run(command + ["complete", "--verdict", "PASS_WITH_ISSUES", *args],
2077
- cwd=work, capture_output=True, text=True)
2077
+ cwd=work, capture_output=True, text=True, encoding="utf-8")
2078
2078
  assert result.returncode == 1 and "BLOCKED:final-report-invalid" in result.stderr, (case, result)
2079
2079
  assert state_path.read_bytes() == before, case
2080
2080
  report.write_bytes(valid)
2081
2081
  result = subprocess.run(command + ["complete", "--verdict", "PASS_WITH_ISSUES", "--log-file", ".devlyn/final-report.md"],
2082
- cwd=work, capture_output=True, text=True)
2082
+ cwd=work, capture_output=True, text=True, encoding="utf-8")
2083
2083
  assert result.returncode == 0, result.stderr
2084
2084
  completed_bytes = state_path.read_bytes()
2085
2085
  completed = read_state(state_path)
@@ -2105,7 +2105,7 @@ def final_report_self_test() -> None:
2105
2105
  report.symlink_to(outside)
2106
2106
  write_state(state_path, candidate)
2107
2107
  before_archive = state_path.read_bytes()
2108
- result = subprocess.run(archive_command, cwd=work, capture_output=True, text=True)
2108
+ result = subprocess.run(archive_command, cwd=work, capture_output=True, text=True, encoding="utf-8")
2109
2109
  assert result.returncode == 1 and "error: archive blocked:" in result.stderr, (case, result)
2110
2110
  assert state_path.read_bytes() == before_archive, case
2111
2111
  assert not (devlyn / "runs").exists(), case
@@ -2116,7 +2116,7 @@ def final_report_self_test() -> None:
2116
2116
  report.unlink()
2117
2117
  report.write_bytes(valid)
2118
2118
  state_path.write_bytes(completed_bytes)
2119
- result = subprocess.run(archive_command, cwd=work, capture_output=True, text=True)
2119
+ result = subprocess.run(archive_command, cwd=work, capture_output=True, text=True, encoding="utf-8")
2120
2120
  assert result.returncode == 0, result.stderr
2121
2121
  target = devlyn / "runs" / run_id
2122
2122
  assert (target / "final-report.md").read_bytes() == valid
@@ -2138,7 +2138,7 @@ def self_test() -> int:
2138
2138
  (devlyn / "engines.json").write_bytes(helper["encoded"](config))
2139
2139
  state = {"version": "3.0", "engine": "codex", "engine_source": "default", "phases": {}}
2140
2140
  frozen = freeze_roles(state, work, "codex")
2141
- (devlyn / "engines.json").write_text('{"executor":"claude"}')
2141
+ (devlyn / "engines.json").write_text('{"executor":"claude"}', encoding="utf-8")
2142
2142
  assert freeze_roles(state, work, "codex") == frozen
2143
2143
  do_spawn(state, "verify", 0, None, None, None, None, devlyn=devlyn)
2144
2144
  assert state["engine"] == "codex" and state["phases"]["verify"]["engine"] == "claude"
@@ -2154,10 +2154,10 @@ def self_test() -> int:
2154
2154
  argv = ["--json", "-m", "gpt-6-astra", "-c", "model_reasoning_effort=high", "task"]
2155
2155
  receipt = {"argv_sha256": hashlib.sha256(json.dumps(argv, separators=(",", ":")).encode()).hexdigest()}
2156
2156
  path = devlyn / "implement.argv.0.json"
2157
- path.write_text(json.dumps(argv))
2157
+ path.write_text(json.dumps(argv), encoding="utf-8")
2158
2158
  binding = bind_worker_role_argv(state, "implement", {"round": 0}, devlyn, receipt)
2159
2159
  assert binding["effort_requested"] == "high"
2160
- path.write_text(json.dumps([*argv, "extra"]))
2160
+ path.write_text(json.dumps([*argv, "extra"]), encoding="utf-8")
2161
2161
  try:
2162
2162
  bind_worker_role_argv(state, "implement", {"round": 0}, devlyn, receipt)
2163
2163
  except ValueError:
@@ -2193,6 +2193,7 @@ def self_test() -> int:
2193
2193
  "--phase", "plan", *event_args,
2194
2194
  ],
2195
2195
  capture_output=True, text=True, check=False,
2196
+ encoding="utf-8",
2196
2197
  )
2197
2198
 
2198
2199
  def assert_plan_rejected_unchanged(
@@ -2340,6 +2341,7 @@ def self_test() -> int:
2340
2341
  "--phase", "build_gate", "spawn", "--round", "0",
2341
2342
  ],
2342
2343
  capture_output=True, text=True, check=False,
2344
+ encoding="utf-8",
2343
2345
  )
2344
2346
  assert blocked_plan_mutation.returncode != 0
2345
2347
  assert "BLOCKED:plan-integrity-mismatch" in blocked_plan_mutation.stderr
@@ -2491,6 +2493,7 @@ def self_test() -> int:
2491
2493
  "--phase", "implement", event, *event_args,
2492
2494
  ],
2493
2495
  cwd=evidence_work, capture_output=True, text=True, check=False,
2496
+ encoding="utf-8",
2494
2497
  )
2495
2498
 
2496
2499
  for event_args in (
@@ -2514,6 +2517,7 @@ def self_test() -> int:
2514
2517
  "--phase", "implement", "--id", "red-first",
2515
2518
  ],
2516
2519
  cwd=evidence_work, capture_output=True, text=True, check=False,
2520
+ encoding="utf-8",
2517
2521
  )
2518
2522
  assert captured.returncode == 0, captured.stderr
2519
2523
  manifest_rel = loads_strict_json(captured.stdout)["manifest_path"]
@@ -2553,6 +2557,7 @@ def self_test() -> int:
2553
2557
  "--phase", "build_gate", event, *event_args,
2554
2558
  ],
2555
2559
  cwd=evidence_work, capture_output=True, text=True, check=False,
2560
+ encoding="utf-8",
2556
2561
  )
2557
2562
 
2558
2563
  results_path = evidence_devlyn / "spec-verify.results.json"
@@ -2778,6 +2783,7 @@ def self_test() -> int:
2778
2783
  [sys.executable, str(pathlib.Path(__file__).resolve()),
2779
2784
  "--devlyn-dir", ".devlyn", "--phase", "build_gate", *args],
2780
2785
  cwd=work, env=cli_env, capture_output=True, text=True, check=False,
2786
+ encoding="utf-8",
2781
2787
  )
2782
2788
 
2783
2789
  def rejected(*args: str, error: str = "BLOCKED:process-evidence-invalid") -> None:
@@ -3126,6 +3132,7 @@ def self_test() -> int:
3126
3132
  "--next-round", "0", "--next-engine", "claude",
3127
3133
  ],
3128
3134
  capture_output=True, text=True,
3135
+ encoding="utf-8",
3129
3136
  )
3130
3137
  assert cli_transition.returncode == 0, cli_transition.stderr
3131
3138
  cli_receipt = loads_strict_json(cli_transition.stdout)
@@ -3372,6 +3379,7 @@ def self_test() -> int:
3372
3379
  subprocess.run(["git", "commit", "-qm", "base"], cwd=work, check=True)
3373
3380
  pre_sha = subprocess.run(
3374
3381
  ["git", "rev-parse", "HEAD"], cwd=work, check=True, capture_output=True, text=True,
3382
+ encoding="utf-8",
3375
3383
  ).stdout.strip()
3376
3384
  work_devlyn = work / ".devlyn"
3377
3385
  work_devlyn.mkdir()
@@ -3441,6 +3449,7 @@ def self_test() -> int:
3441
3449
  "--authorized-surface-json", json.dumps([entry]),
3442
3450
  ],
3443
3451
  cwd=work, capture_output=True, text=True,
3452
+ encoding="utf-8",
3444
3453
  )
3445
3454
  if (
3446
3455
  surface_check.returncode == 0
@@ -3830,7 +3839,7 @@ def self_test() -> int:
3830
3839
  else:
3831
3840
  raise AssertionError("SURFACE_CLOSE opened BUILD_GATE without native evidence")
3832
3841
  assert transition_state == inherited_surface
3833
- wrapper_log.write_text('{"modelUsage":{"different-model":{"inputTokens":1}}}\n')
3842
+ wrapper_log.write_text('{"modelUsage":{"different-model":{"inputTokens":1}}}\n', encoding="utf-8")
3834
3843
  mismatch = copy.deepcopy(surface_state)
3835
3844
  assert "model-attestation-mismatch" in do_complete(
3836
3845
  mismatch, "surface_close", "PASS", None, None, None, None, None,
@@ -4348,6 +4357,7 @@ def self_test() -> int:
4348
4357
  [sys.executable, script, "--devlyn-dir", str(receipt_devlyn),
4349
4358
  "--phase", phase, *event_args],
4350
4359
  cwd=receipt_work, capture_output=True, text=True, check=False,
4360
+ encoding="utf-8",
4351
4361
  )
4352
4362
 
4353
4363
  write_state(receipt_state_path, {
@@ -4421,6 +4431,7 @@ def self_test() -> int:
4421
4431
  archived = subprocess.run(
4422
4432
  [sys.executable, str(pathlib.Path(script).with_name("archive_run.py"))],
4423
4433
  cwd=archive_work, capture_output=True, text=True,
4434
+ encoding="utf-8",
4424
4435
  )
4425
4436
  assert archived.returncode == 0, archived.stderr
4426
4437
  destination = archive_devlyn / "runs" / terminal_state["run_id"]
@@ -4608,6 +4619,7 @@ def self_test() -> int:
4608
4619
  return subprocess.run(
4609
4620
  ["git", "rev-parse", "HEAD"], cwd=repo, check=True,
4610
4621
  capture_output=True, text=True,
4622
+ encoding="utf-8",
4611
4623
  ).stdout.strip()
4612
4624
 
4613
4625
  def durability_fixture(
@@ -4668,6 +4680,7 @@ def self_test() -> int:
4668
4680
  assert subprocess.run(
4669
4681
  ["git", "show", "-s", "--format=%s", "HEAD"], cwd=exact_repo,
4670
4682
  check=True, capture_output=True, text=True,
4683
+ encoding="utf-8",
4671
4684
  ).stdout.strip() == "chore(pipeline): closure-restore round 1"
4672
4685
  gate_spec = importlib.util.spec_from_file_location(
4673
4686
  "f7_carrier_gate", pathlib.Path(__file__).resolve().parents[3]
@@ -4794,6 +4807,7 @@ def self_test() -> int:
4794
4807
  "--devlyn-dir", ".devlyn", "--phase", "verify", "spawn",
4795
4808
  "--round", "1"],
4796
4809
  cwd=target_repo, capture_output=True, text=True,
4810
+ encoding="utf-8",
4797
4811
  )
4798
4812
  assert reentry.returncode != 0
4799
4813
  assert "closure-durability-receipt" in reentry.stderr
@@ -4813,12 +4827,14 @@ def self_test() -> int:
4813
4827
  [sys.executable, str(pathlib.Path(__file__).resolve()),
4814
4828
  "--devlyn-dir", ".devlyn", "--phase", "build_gate", "spawn", "--round", "1"],
4815
4829
  cwd=skipped_repo, capture_output=True, text=True,
4830
+ encoding="utf-8",
4816
4831
  )
4817
4832
  assert skipped.returncode != 0 and "checkpoint receipt is missing" in skipped.stderr
4818
4833
  assert not (skipped_devlyn / "closure-durability.round-1.json").exists()
4819
4834
  assert subprocess.run(
4820
4835
  ["git", "rev-parse", "HEAD"], cwd=skipped_repo, check=True,
4821
4836
  capture_output=True, text=True,
4837
+ encoding="utf-8",
4822
4838
  ).stdout.strip() == skipped_fix
4823
4839
 
4824
4840
  receipt_repo, receipt_devlyn, receipt_state, receipt_fix, _ = durability_fixture(
@@ -5180,4 +5196,5 @@ def main() -> int:
5180
5196
 
5181
5197
 
5182
5198
  if __name__ == "__main__":
5199
+ runpy.run_path(str(pathlib.Path(__file__).with_name("platform-support.py")))["configure_utf8"]()
5183
5200
  raise SystemExit(main())