devlyn-cli 3.1.1 → 3.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -282,17 +282,14 @@ def init_spec_source(
282
282
  block("BLOCKED:invalid-flags", f"spec unreadable: {raw_path}: {exc}")
283
283
  helper = shared_dir / "spec-verify-check.py"
284
284
  expected = path.with_name("spec.expected.json")
285
- if expected.is_file():
286
- run_checked([sys.executable, str(helper), "--check-expected", str(expected)], cwd)
287
- else:
288
- run_checked([sys.executable, str(helper), "--check", str(path)], cwd)
289
285
  module = load_spec_helper(shared_dir)
290
286
  if expected.is_file():
291
287
  found, _staged, error, _expected_path, _data = module.stage_from_expected(path, staging_dir)
292
288
  if not found or error:
293
289
  block("BLOCKED:invalid-flags", error or f"expected contract not found: {expected}")
294
290
  else:
295
- _staged, error = module.stage_from_source(path, staging_dir)
291
+ run_checked([sys.executable, str(helper), "--check", str(path)], cwd)
292
+ _found, _staged, error = module.stage_from_source(path, staging_dir)
296
293
  if error:
297
294
  block("BLOCKED:invalid-flags", error)
298
295
  staged_path = staging_dir / "spec-verify.json"
@@ -336,7 +333,7 @@ def capture_external_diff(cwd: pathlib.Path, ref: str) -> bytes:
336
333
  if source.is_file():
337
334
  raw = source.read_bytes()
338
335
  else:
339
- proc = subprocess.run(["git", "diff", "--binary", ref], cwd=cwd, capture_output=True)
336
+ proc = subprocess.run(["git", "diff", "--binary", "--no-ext-diff", "--src-prefix=a/", "--dst-prefix=b/", ref], cwd=cwd, capture_output=True)
340
337
  if proc.returncode != 0:
341
338
  block("BLOCKED:invalid-flags", os.fsdecode(proc.stderr or proc.stdout).strip())
342
339
  raw = proc.stdout
@@ -1187,6 +1184,16 @@ def self_test() -> int:
1187
1184
  staged = strict_json((work / ".devlyn" / "spec-verify.json").read_text(encoding="utf-8"))
1188
1185
  assert staged["verification_commands"][0]["cmd"] == "printf ok"
1189
1186
  assert spec_result["source"]["spec_sha256"] == sha256(spec_raw)
1187
+ pure_spec = spec_dir / "design.md"
1188
+ pure_spec.write_text(
1189
+ "# Design\n\n<!-- devlyn:verification -->\n## Verification\n\n"
1190
+ '```json\n{"pure_design":true,"verification_commands":[]}\n```\n',
1191
+ encoding="utf-8",
1192
+ )
1193
+ complete_prior(work)
1194
+ pure_result = bootstrap(["--spec", str(pure_spec.relative_to(work))], work, script_shared)
1195
+ assert pure_result["source"]["spec_sha256"] == sha256(pure_spec.read_bytes())
1196
+ assert not (work / ".devlyn/spec-verify.json").exists()
1190
1197
  external_patch.write_bytes(b"stale free-form patch\n")
1191
1198
  complete_prior(work)
1192
1199
  bootstrap(["fresh", "goal"], work, script_shared)
@@ -1208,8 +1215,46 @@ def self_test() -> int:
1208
1215
  assert verify_result["mode"] == "verify-only"
1209
1216
  assert external_patch.read_bytes() == patch_raw
1210
1217
  subprocess.run(["git", "restore", "app.py"], cwd=work, check=True)
1218
+ # Ambient no-prefix config must not change the reader's a/b contract.
1219
+ (work / "app.py").write_text("print('prefix control')\n", encoding="utf-8")
1220
+ settings = ["diff.noprefix"] + (["diff.external"] if os.name != "nt" else [])
1221
+ for setting in settings:
1222
+ subprocess.run(["git", "config", setting, "true"], cwd=work, check=True)
1223
+ try:
1224
+ assert b"diff --git a/app.py b/app.py\n" in capture_external_diff(work, "HEAD")
1225
+ finally:
1226
+ subprocess.run(["git", "config", "--unset", setting], cwd=work, check=True)
1227
+ subprocess.run(["git", "restore", "app.py"], cwd=work, check=True)
1211
1228
  print("PASS bootstrap self-test patch lifecycle: full-mode removal + dirty verify-only exact capture")
1212
1229
 
1230
+ for valid_named in (True, False):
1231
+ named_work = root / f"named-spec-{valid_named}"
1232
+ init_repo(named_work)
1233
+ named_spec = named_work / "X.md"
1234
+ named_spec.write_text(
1235
+ "---\ncomplexity: " + ("medium" if valid_named else "hihg") + "\n---\n# Named spec\n",
1236
+ encoding="utf-8",
1237
+ )
1238
+ (named_work / "spec.md").write_text(
1239
+ "---\ncomplexity: " + ("hihg" if valid_named else "medium") + "\n---\n# Unrelated spec\n",
1240
+ encoding="utf-8",
1241
+ )
1242
+ (named_work / "spec.expected.json").write_text(json.dumps({
1243
+ "verification_commands": [{"cmd": "printf named-contract"}],
1244
+ }), encoding="utf-8")
1245
+ try:
1246
+ result = bootstrap(["--spec", "X.md"], named_work, script_shared)
1247
+ except BootstrapBlocked as exc:
1248
+ assert not valid_named and exc.reason == "BLOCKED:invalid-flags", exc
1249
+ assert "frontmatter complexity" in exc.detail, exc.detail
1250
+ assert not (named_work / ".devlyn").exists()
1251
+ else:
1252
+ assert valid_named, "invalid named spec was accepted via unrelated spec.md"
1253
+ assert result["source"]["spec_path"] == "X.md"
1254
+ assert result["source"]["spec_sha256"] == sha256(named_spec.read_bytes())
1255
+ staged = strict_json((named_work / ".devlyn/spec-verify.json").read_text(encoding="utf-8"))
1256
+ assert staged["verification_commands"] == [{"cmd": "printf named-contract"}]
1257
+
1213
1258
  dirty_work = root / "dirty-repo"
1214
1259
  init_repo(dirty_work)
1215
1260
  bootstrap(["clean", "baseline"], dirty_work, script_shared)