devlyn-cli 4.2.12 → 4.2.14
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.
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
"paragraphs": {
|
|
83
83
|
"AGENTS.md": [
|
|
84
84
|
"00044037bbe954aacee48c5edbe01b53aabef90fbe3f0c28c9136dc428fdd7df",
|
|
85
|
+
"0031285b60a5cf5e965df50482969315e5e62b163bb706695622fb4ed63f3dda",
|
|
85
86
|
"0042d7a45818c8e1c451ac255502ee014fda81a2dbd2a4c04e75a66bc3493940",
|
|
86
87
|
"0144d44eb6a0537211159e5f15baf2cacf11cc3275490ac5d295917df22cff27",
|
|
87
88
|
"01e073f6a5722534affe03ab334fe4a394347f13de5ce191b757a0b7c6f51904",
|
|
@@ -91,6 +92,7 @@
|
|
|
91
92
|
"03d85ed66f64ea52f2f96d9fb203b05ac6afc0ba634a4dfb3585429ce1531c44",
|
|
92
93
|
"0467080d13d933b663362006ae5f5a0ad73baaed51ad50faf3f0d3d9f92244a3",
|
|
93
94
|
"0634a245075d1a5aac4e58c1345f40f5f4c3b4909315f86aacd7ab239ea3ce61",
|
|
95
|
+
"072b63f736ee80f28550aec03277f033268a232e349ac86c3f1cab4bad9006c0",
|
|
94
96
|
"0869633a322da8f8d800b1d2c23becc52f5ff592f0823533ea24f09de465c53a",
|
|
95
97
|
"090f003bb30b98e6979865f3eb9bbc66eaa8f0cd74a89ff2eea5ef38cbee13b5",
|
|
96
98
|
"0926622dde30d21c6cafe6b69b0bb0608b31e2528655615c7edbdedc6ae43d7c",
|
|
@@ -163,6 +165,7 @@
|
|
|
163
165
|
"3e87f3023f1ee4810c769a27f63244a4c4825fa3c78417abd8a1a4bfb6350134",
|
|
164
166
|
"3efd55e3caac3e3488b81921446bc9ca4ec92ac766abbcfefafeb183b21464a1",
|
|
165
167
|
"3f05e197006cf9715612c67cb8709690e053262e9a0702723e69798ad29a2c08",
|
|
168
|
+
"3fde1f80f1d0e17ba79a674cb1124b8034df92801e9615c8fa443ce7c7a7672b",
|
|
166
169
|
"41323b1c12be256f604edf8c49ff505acbaa75b6a1b938b76286d5eebec1ce95",
|
|
167
170
|
"41660630582e14f984686f201e6af130e29bbd3fadc3c76a3c6055a7a8f78c4f",
|
|
168
171
|
"41d2ff119ff97961c64e5eb57632c2d04b22794b50eb9227506c4ed763ebca3f",
|
|
@@ -374,6 +377,7 @@
|
|
|
374
377
|
"e237f12ed08800ccc4c6eb4ae57cdd59d61198711f6a3b02dcb668aa08fddb93",
|
|
375
378
|
"e289dbab99dadb30d8315b32b8243154148c82955f238936bce4c1fe1c3ae525",
|
|
376
379
|
"e2bac40f3ff0b39726f95caaa9b19aa3b21596f165ac7a9dc68ebf42681d58cb",
|
|
380
|
+
"e389e1e77cc2da32214e95451b25b45402b1b586b26f5688bb373c492db3fcfb",
|
|
377
381
|
"e3ee2da50e961046da3f5b68b0c328570307fb21a93424ae22536b8f216b55a3",
|
|
378
382
|
"e43e4b5b86044fbf7be8d7fd1e3e27cd2089bfc8243e07dc2c3fc034c19794ea",
|
|
379
383
|
"e44015948c7d04e60f3fed7e348c2052538e7784d8fe7ea3edf9f52de07448dd",
|
|
@@ -652,6 +656,7 @@
|
|
|
652
656
|
"6170d780ab4f3cb5e2ea7d2757f9239d35dbbc1f9398e73877a95d5ef4c582ee",
|
|
653
657
|
"624becc31a3b8619bab2b863a469b8bc6b19fc48acaa7d59a455057ff8d7cce1",
|
|
654
658
|
"635097bbe52855009ba78ab992a905aad63b755a4b5c688179d8d597d5c9da01",
|
|
659
|
+
"65997521bd2bee492eeadc8570bf71f374cf1a05856d4acca499350b3db0dc7c",
|
|
655
660
|
"65b903f779d8879f04e427a400a04bd29ab982ab28a62c2c172620773518242b",
|
|
656
661
|
"6639e9e9622eca3d978923f26bf6f6ba1a95be4059206cc27c62e9deb026146a",
|
|
657
662
|
"6693baa07666a5853ea606c25812f70550d28f6182d9c9dbfc1421270f1ec544",
|
|
@@ -665,6 +670,7 @@
|
|
|
665
670
|
"6a08a11aea6a1dfaa99823a73bc8730e8fff9c483ec8989aaeca669cfc7b3ed6",
|
|
666
671
|
"6a27907ba14fae9ddcacd551007b783b2f813c51c118828c74fd2174e09a7d41",
|
|
667
672
|
"6a5ea10aa15a05ac992bd118adc3c0de48c85cea504aef655065d9678f93717c",
|
|
673
|
+
"6b098e67dbcdbec4569fb950f2067e4c4843a3b5f26a4e8886820102542eddee",
|
|
668
674
|
"6b26d02b3f87d53a964b6fe405992104dd2ee85f8c1b3048951ece574d9005a8",
|
|
669
675
|
"6b65f3883cfca3d531ab716c069c88f3a7d964657c5e026bda25eacdb37f16c5",
|
|
670
676
|
"6b726d35cdc0851401769b6de9c422cfa475dbe7c0d036726706439a2cdbb02a",
|
|
@@ -699,6 +705,7 @@
|
|
|
699
705
|
"7b47b07e3062f3dc839f11397bc3265120e01a35b0fda094d738e28a6729e6c3",
|
|
700
706
|
"7bb5c341115c56592c0d1b5903d01321ce9c324136bc53babf9d438389796d94",
|
|
701
707
|
"7c0dacd603b4381539d6d3cf21f5fe625cf54b05eb4aa750fce658529b11d81d",
|
|
708
|
+
"7c6c98c3dc37aa0760796051b0b81ac10b9ebad7c9d7da3615b3fd764e589766",
|
|
702
709
|
"7c79750557c7d7156df6f135fb582ec09cc5f4987ac8e2af0f5113e42c117224",
|
|
703
710
|
"7d083db061c678113c12453eb73299caa9e207dd6418ad63406154fd330f3309",
|
|
704
711
|
"7d2239e2cd4893c57ac0bf1373a2ef46419f614d0ce18a12df51222bc7795327",
|
|
@@ -916,6 +923,7 @@
|
|
|
916
923
|
"e2070548ec9a6244c569ef0b9a43c2e5dd99cf9c0fa4540a21fd0595b291e444",
|
|
917
924
|
"e29fec7d54a144ea4867a3764fccdee6849d58d0f32eacdd56b2c317863fa1c6",
|
|
918
925
|
"e2e0fc585321ee2013cccaed95d47c49fbca71b54c67abad9080d19d70e3c1a5",
|
|
926
|
+
"e389e1e77cc2da32214e95451b25b45402b1b586b26f5688bb373c492db3fcfb",
|
|
919
927
|
"e3b2f7be15a793f07cbe8e51aa2feeac51d4c67077b94e5e20bad2e14be07fc5",
|
|
920
928
|
"e468c242cc38c390f9679fca61f355848f58d51570249dfbb65b4f2e26a9dfb1",
|
|
921
929
|
"e4bafa0f553e899b65f05feabf4cbe86d8246c57cbba745eb79e6aa858f40a6b",
|
package/bin/skill-history.json
CHANGED
|
@@ -334,6 +334,7 @@
|
|
|
334
334
|
"a3b852c30898b572021b32721638f67d9f44cae98653da3362456a7b88208de7",
|
|
335
335
|
"a7d7d65fd5f672cb404e6069fdc42ad7077deaa80ed8500cfa8b382dab2e3e29",
|
|
336
336
|
"aba66b674683935e4c28c93333896dc116ffe67e7a35fe30b3599a8be507e94e",
|
|
337
|
+
"b2e4437fa981317362d03ee032e1cfd8b686316e966c6ba12ecd619a009c80a6",
|
|
337
338
|
"c1b8e3ebf38e2be8972aa003e9614a782cb4f195c69d20b95a475ebf9aaaab9e",
|
|
338
339
|
"ce282a55328c2d071cacc377670fbebea4da4b9a7fee81b25293fb26763f17c9",
|
|
339
340
|
"cf61a917a804503ef1e26fbb1cf498181e843a0758ec001f84edb7bcac837448",
|
|
@@ -267,18 +267,32 @@ def guard_results(packet, expected, source):
|
|
|
267
267
|
results.append({"rule": "forbidden_files", "subject": path, "passed": path not in changed, "blocking": True})
|
|
268
268
|
limit = expected.get("max_deps_added", 0)
|
|
269
269
|
try:
|
|
270
|
-
|
|
271
|
-
subject, passed = f"{added} added, limit {limit}", added <= limit
|
|
270
|
+
before = dependency_names(work, inputs)
|
|
272
271
|
except ValueError as exc:
|
|
273
|
-
subject, passed =
|
|
272
|
+
subject, passed = (f"inputs package.json at {inputs} unreadable; limit cannot apply. "
|
|
273
|
+
f"Fix base: regular npm-valid file; start a new loop. {exc}"), False
|
|
274
|
+
else:
|
|
275
|
+
try:
|
|
276
|
+
added = len(dependency_names(work, source) - before)
|
|
277
|
+
subject, passed = f"{added} added, limit {limit}", added <= limit
|
|
278
|
+
except ValueError as exc:
|
|
279
|
+
subject, passed = f"candidate package.json at {source} is invalid; fix it in the candidate. {exc}", False
|
|
274
280
|
results.append({"rule": "max_deps_added", "subject": subject, "passed": passed, "blocking": True})
|
|
275
281
|
return results
|
|
276
282
|
|
|
277
283
|
|
|
278
284
|
def dependency_names(work, commit):
|
|
279
285
|
"""Package names under dependencies and devDependencies of the root package.json at `commit`; a missing file has none."""
|
|
280
|
-
|
|
286
|
+
entry = git(work, "ls-tree", commit, "--", "package.json").stdout
|
|
287
|
+
if not entry:
|
|
281
288
|
return set()
|
|
289
|
+
mode, kind, _ = entry.split(maxsplit=2)
|
|
290
|
+
if mode not in (b"100644", b"100755") or kind != b"blob":
|
|
291
|
+
kind = "symlink" if mode == b"120000" else "gitlink" if mode == b"160000" else kind.decode("ascii")
|
|
292
|
+
if mode == b"120000":
|
|
293
|
+
target = git(work, "show", f"{commit}:package.json").stdout.decode("utf-8", "backslashreplace")
|
|
294
|
+
kind += f" to {target!r}"
|
|
295
|
+
raise ValueError(f"{kind} (mode {mode.decode('ascii')}); expected a regular file")
|
|
282
296
|
try:
|
|
283
297
|
# Read as npm reads it: a leading BOM is ignored, a duplicate key's last value wins, a null section is empty and
|
|
284
298
|
# NaN or Infinity is invalid.
|
|
@@ -718,7 +732,9 @@ class AcceptanceTests(unittest.TestCase):
|
|
|
718
732
|
moved from dependencies to devDependencies counts 0 and is accepted; an invalid package.json fails the guard,
|
|
719
733
|
naming it. A leading UTF-8 BOM, which npm and Node accept, is ignored: with one at both commits, one added
|
|
720
734
|
counts 1. A duplicate key (its last value wins) and a null dependencies section, which npm also reads, count 0
|
|
721
|
-
and are accepted; NaN, which npm rejects, fails the guard.
|
|
735
|
+
and are accepted; NaN, which npm rejects, fails the guard. Invalid inputs fail before an invalid candidate,
|
|
736
|
+
naming inputs and the base repair/new loop action; invalid candidates name the candidate and its repair.
|
|
737
|
+
The action survives the queue's 200-character first-reason collapse, with parser detail after it."""
|
|
722
738
|
passing = {"verification_commands": [{"argv": [sys.executable, "-c", "pass"], "contract_refs": ["R1"]}]}
|
|
723
739
|
|
|
724
740
|
def guard(inputs, source):
|
|
@@ -743,7 +759,7 @@ class AcceptanceTests(unittest.TestCase):
|
|
|
743
759
|
with self.subTest("invalid"):
|
|
744
760
|
commit, result, deps = guard({"dependencies": {"a": "1"}}, '{"dependencies": {"a": "1",}}\n')
|
|
745
761
|
self.assertEqual((deps["passed"], result["verdict"]), (False, "FAILED"))
|
|
746
|
-
self.
|
|
762
|
+
self.assertTrue(deps["subject"].startswith(f"candidate package.json at {commit} is invalid; fix it in the candidate."))
|
|
747
763
|
with self.subTest("BOM"):
|
|
748
764
|
self.write("package.json", "\ufeff" + json.dumps({"dependencies": {"a": "1"}}))
|
|
749
765
|
_, _, deps = guard(None, "\ufeff" + json.dumps({"dependencies": {"a": "1", "b": "1"}}))
|
|
@@ -755,7 +771,72 @@ class AcceptanceTests(unittest.TestCase):
|
|
|
755
771
|
with self.subTest("NaN"):
|
|
756
772
|
commit, result, deps = guard({"dependencies": {"a": "1"}}, '{"config": {"threshold": NaN}}')
|
|
757
773
|
self.assertEqual((deps["passed"], result["verdict"]), (False, "FAILED"))
|
|
758
|
-
self.
|
|
774
|
+
self.assertTrue(deps["subject"].startswith(f"candidate package.json at {commit} is invalid; fix it in the candidate."))
|
|
775
|
+
with self.subTest("invalid inputs precede invalid candidate"):
|
|
776
|
+
self.write("package.json", "{")
|
|
777
|
+
_, result, deps = guard(None, "[")
|
|
778
|
+
self.assertEqual((deps["passed"], result["verdict"]), (False, "FAILED"))
|
|
779
|
+
self.assertTrue(deps["subject"].startswith(f"inputs package.json at {self.packet['inputs_sha']} unreadable; limit cannot apply."))
|
|
780
|
+
one_line = runpy.run_path(str(Path(__file__).with_name("queue.py")))["one_line"]
|
|
781
|
+
for size in (40, 64):
|
|
782
|
+
reason = result["reasons"][0].replace(self.packet["inputs_sha"], "a" * size)
|
|
783
|
+
self.assertIn("Fix base: regular npm-valid file; start a new loop", one_line(reason))
|
|
784
|
+
|
|
785
|
+
@unittest.skipIf(os.name == "nt", "a symbolic link needs a privilege on Windows")
|
|
786
|
+
def test_max_deps_added_rejects_symlinks(self):
|
|
787
|
+
"""Prediction: a candidate package.json symlink to a file named '{}' containing a dependency fails the guard,
|
|
788
|
+
naming the candidate, symlink and target; before, the target text parsed as an empty object and passed limit 0.
|
|
789
|
+
An ordinary candidate symlink also names its target and candidate repair. An inputs symlink fails first even
|
|
790
|
+
when the candidate is invalid JSON, naming inputs and the base repair/new loop action."""
|
|
791
|
+
passing = {"verification_commands": [{"argv": [sys.executable, "-c", "pass"], "contract_refs": ["R1"]}]}
|
|
792
|
+
package = self.work / "package.json"
|
|
793
|
+
one_line = runpy.run_path(str(Path(__file__).with_name("queue.py")))["one_line"]
|
|
794
|
+
for side, target in (("candidate", "{}"), ("inputs", "deps.json"), ("candidate", "deps.json")):
|
|
795
|
+
with self.subTest(side=side, target=target):
|
|
796
|
+
package.unlink(missing_ok=True)
|
|
797
|
+
self.write(target, json.dumps({"dependencies": {"hidden": "1"}}))
|
|
798
|
+
if side == "inputs":
|
|
799
|
+
package.symlink_to(target)
|
|
800
|
+
else:
|
|
801
|
+
self.write("package.json", "{}")
|
|
802
|
+
self.inputs(passing)
|
|
803
|
+
package.unlink()
|
|
804
|
+
if side == "candidate":
|
|
805
|
+
package.symlink_to(target)
|
|
806
|
+
else:
|
|
807
|
+
self.write("package.json", "[")
|
|
808
|
+
source = self.commit("product")
|
|
809
|
+
result = self.submit(source)
|
|
810
|
+
deps = next(g for g in result["guards"] if g["rule"] == "max_deps_added")
|
|
811
|
+
self.assertEqual((deps["passed"], result["verdict"]), (False, "FAILED"))
|
|
812
|
+
commit = self.packet["inputs_sha"] if side == "inputs" else source
|
|
813
|
+
self.assertTrue(deps["subject"].startswith(f"{side} package.json at {commit}"))
|
|
814
|
+
self.assertIn(f"symlink to {target!r}", deps["subject"])
|
|
815
|
+
action = "Fix base: regular npm-valid file; start a new loop" if side == "inputs" else "fix it in the candidate."
|
|
816
|
+
self.assertIn(action, one_line(result["reasons"][0]))
|
|
817
|
+
|
|
818
|
+
def test_max_deps_added_requires_regular_blobs(self):
|
|
819
|
+
"""Prediction: both regular blob modes count names; a tree or gitlink at package.json fails the guard with
|
|
820
|
+
its kind and candidate repair, rather than escaping as an AcceptanceError without a guard subject."""
|
|
821
|
+
self.write("package.json", '{"dependencies": {"a": "1"}}')
|
|
822
|
+
self.inputs()
|
|
823
|
+
inputs = self.packet["inputs_sha"]
|
|
824
|
+
blob = self.g("rev-parse", f"{inputs}:package.json")
|
|
825
|
+
tree = self.g("rev-parse", f"{inputs}^{{tree}}")
|
|
826
|
+
for mode, kind, oid in (("100644", "blob", blob), ("100755", "blob", blob),
|
|
827
|
+
("040000", "tree", tree), ("160000", "commit", inputs)):
|
|
828
|
+
with self.subTest(mode=mode):
|
|
829
|
+
made = subprocess.run(["git", "-C", str(self.work), "mktree"],
|
|
830
|
+
input=f"{mode} {kind} {oid}\tpackage.json\n".encode(), capture_output=True, check=True)
|
|
831
|
+
source = self.g("commit-tree", made.stdout.decode().strip(), "-p", inputs, "-m", "package kind")
|
|
832
|
+
deps = next(g for g in guard_results(self.packet, {}, source) if g["rule"] == "max_deps_added")
|
|
833
|
+
if kind == "blob":
|
|
834
|
+
self.assertEqual(dependency_names(self.work, source), {"a"})
|
|
835
|
+
self.assertEqual((deps["passed"], deps["subject"]), (True, "0 added, limit 0"))
|
|
836
|
+
else:
|
|
837
|
+
self.assertFalse(deps["passed"])
|
|
838
|
+
self.assertTrue(deps["subject"].startswith(f"candidate package.json at {source} is invalid; fix it in the candidate."))
|
|
839
|
+
self.assertIn("gitlink" if kind == "commit" else kind, deps["subject"])
|
|
759
840
|
|
|
760
841
|
|
|
761
842
|
def main():
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devlyn-cli",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.14",
|
|
4
4
|
"description": "Loop & harness engineering for AI coding agents (Claude Code, Codex, and any AGENTS.md-driven agent) — core engineering principles for CLAUDE.md/AGENTS.md and a loop designer that splits one intent into tasks agents drain hands-free",
|
|
5
5
|
"homepage": "https://github.com/fysoul17/devlyn-cli#readme",
|
|
6
6
|
"bin": {
|