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.
- package/AGENTS.md +6 -6
- package/CLAUDE.md +7 -7
- package/README.md +20 -5
- package/bin/devlyn.js +10 -52
- package/bin/instruction-templates.json +52 -0
- package/bin/instructions.js +137 -0
- package/config/skills/_shared/adapters/claude.md +1 -5
- package/config/skills/_shared/adapters/codex.md +1 -5
- package/config/skills/_shared/adapters/omp.md +0 -4
- package/config/skills/_shared/expected.schema.json +2 -2
- package/config/skills/_shared/platform-support.py +4 -1
- package/config/skills/_shared/process-evidence.py +16 -1
- package/config/skills/_shared/resolve-bootstrap.py +51 -6
- package/config/skills/_shared/spec-verify-check.py +525 -89
- package/config/skills/_shared/state-phase-write.py +202 -32
- package/config/skills/_shared/task-complete.py +260 -16
- package/config/skills/devlyn:ideate/SKILL.md +1 -1
- package/config/skills/devlyn:ideate/references/from-spec-mode.md +1 -1
- package/config/skills/devlyn:ideate/references/spec-template.md +3 -3
- package/config/skills/devlyn:resolve/SKILL.md +4 -4
- package/config/skills/devlyn:resolve/references/free-form-mode.md +2 -2
- package/config/skills/devlyn:resolve/references/phases/build-gate.md +4 -3
- package/config/skills/devlyn:resolve/references/phases/verify.md +9 -7
- package/config/skills/devlyn:resolve/references/state-schema.md +2 -2
- package/config/skills/devlyn:resolve/references/task-completion.md +27 -0
- package/package.json +1 -1
- package/scripts/lint-skills.sh +2 -2
|
@@ -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
|
-
|
|
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)
|