devlyn-cli 4.2.11 → 4.2.13
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/bin/skill-history.json +3 -0
- package/config/skills/_shared/task-completion.md +0 -6
- package/config/skills/devlyn-ideate/SKILL.md +1 -1
- package/config/skills/devlyn-ideate/references/loop.md +3 -3
- package/config/skills/devlyn-ideate/scripts/acceptance.py +88 -7
- package/config/skills/devlyn-ideate/scripts/queue.py +12 -4
- package/package.json +1 -1
package/bin/skill-history.json
CHANGED
|
@@ -151,6 +151,7 @@
|
|
|
151
151
|
"f12077a8c5f5b6f08c1e220b6f4a191eca8de0daf35fd8ce060ec7a45bb700ff",
|
|
152
152
|
"f133dd1c3c40f6b4cf6879285cf282dcac436a99479717d8cb6968ad885ce0a2",
|
|
153
153
|
"f1b0f66a2079af7423e0889435dc3bc1e5e14c20e7d29d4d55a56217cdb036ee",
|
|
154
|
+
"f1b9e7216e6ba6cd39999326239c7e4ba96d0339beb4b68579800f11b1b28954",
|
|
154
155
|
"f22d5b4ebe5f973cbbd5a1fc747c068915e0b4a5b0326754f6a750065091853c",
|
|
155
156
|
"f2bcfa0471aba81a10e3d0e6fd43bd6ec670601a4ba3a7d719e595edf9ab032a",
|
|
156
157
|
"f958bfabd2e056fea911526fba2196d2ed5a48828f576e5ce1657100b466db20",
|
|
@@ -301,6 +302,7 @@
|
|
|
301
302
|
"0098d266ee9d7c79c0c0a0c30474e9bf13fa0c76911668b79206985136c94f80",
|
|
302
303
|
"0122885c08069474ca4a0a5825f885364a62938885f0e501c09242d176c34b08",
|
|
303
304
|
"05e824f63a6381e02042afb1df0f1437584e25a80de8e9db0bd9456aaeb7aab8",
|
|
305
|
+
"0601ee535419b7039d5da34bd9c8e1c2b3631307214e4afaf394dd47494090c7",
|
|
304
306
|
"0cef055580e00f9ed439eacb2dff9d238bb11619457472016095a8915162fa4b",
|
|
305
307
|
"0dc69c20434966015176d83baa6f95b3421f9e13d0b1bd608a22f0560eae1049",
|
|
306
308
|
"0fe9b270d670f70da9d2535969b6117684b7c1517806b3029ebaf9624da8006c",
|
|
@@ -332,6 +334,7 @@
|
|
|
332
334
|
"a3b852c30898b572021b32721638f67d9f44cae98653da3362456a7b88208de7",
|
|
333
335
|
"a7d7d65fd5f672cb404e6069fdc42ad7077deaa80ed8500cfa8b382dab2e3e29",
|
|
334
336
|
"aba66b674683935e4c28c93333896dc116ffe67e7a35fe30b3599a8be507e94e",
|
|
337
|
+
"b2e4437fa981317362d03ee032e1cfd8b686316e966c6ba12ecd619a009c80a6",
|
|
335
338
|
"c1b8e3ebf38e2be8972aa003e9614a782cb4f195c69d20b95a475ebf9aaaab9e",
|
|
336
339
|
"ce282a55328c2d071cacc377670fbebea4da4b9a7fee81b25293fb26763f17c9",
|
|
337
340
|
"cf61a917a804503ef1e26fbb1cf498181e843a0758ec001f84edb7bcac837448",
|
|
@@ -159,12 +159,6 @@ resume its delivery. Allocate a replacement from the current remote base,
|
|
|
159
159
|
include only the still-needed task changes and correction, and verify and
|
|
160
160
|
accept it anew. If the predecessor has merged, make a new follow-up instead.
|
|
161
161
|
|
|
162
|
-
Queue drains follow ideate's loop protocol
|
|
163
|
-
(`../devlyn-ideate/references/loop.md`): its evidence-derived `loop` result is
|
|
164
|
-
bound with `accept` before the queue-only terminal commit is attached with
|
|
165
|
-
`attach`, and dependent local tasks allocate from the accepted predecessor's
|
|
166
|
-
source. Failed results keep custody and a recovery ref but are never published.
|
|
167
|
-
|
|
168
162
|
## Deliver and resume
|
|
169
163
|
|
|
170
164
|
Wait for your children, stop your dev servers and task writers, then leave the
|
|
@@ -92,7 +92,7 @@ A removed flag stops with its instruction and selects no other behavior.
|
|
|
92
92
|
|
|
93
93
|
## add
|
|
94
94
|
|
|
95
|
-
An absolute path to a package's `meta.md` is added as it is; anything else is planned first. Add with `python3 "$DEVLYN_SKILL_DIR/scripts/queue.py" add '<absolute meta.md>'`, the only writer of a loop's queue file `docs/specs/<loop-id>/queue.md`; never edit one by hand. In every delivery mode add makes no commit on the checked-out branch and leaves the branch, the index and tracked files as they are: it holds the package and its rows at `refs/devlyn/captures/<loop-id>`, records the add and removes the package copies equal to that capture. Report the capture ref and its `git show` command, any `cleanup` entry, and that a revision needs a new loop id.
|
|
95
|
+
An absolute path to a package's `meta.md` is added as it is; anything else is planned first. Add with `python3 "$DEVLYN_SKILL_DIR/scripts/queue.py" add '<absolute meta.md>'`, the only writer of a loop's queue file `docs/specs/<loop-id>/queue.md`; never edit one by hand. In every delivery mode add makes no commit on the checked-out branch and leaves the branch, the index and tracked files as they are: it holds the package and its rows at `refs/devlyn/captures/<loop-id>`, records the add and removes the package copies equal to that capture. Report the capture ref and its `git show` command, any `cleanup` entry, and that a revision needs a new loop id. A retry of the same add reports it as added, also once the copies are gone. Tasks start from committed state, a local loop from the branch it was added on and `auto`/`pr` from the remote base, so commit, and for `auto`/`pr` push, whatever the plan depends on. A pending legacy raw-intent row of `docs/specs/queue.md` is replaced in place by a package whose `## Intent` reproduces the row verbatim, added with `--materialize <line>`.
|
|
96
96
|
|
|
97
97
|
## status
|
|
98
98
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Loop protocol
|
|
2
2
|
|
|
3
|
-
`drain` runs the queue serially. Each eligible task gets an owned worktree, committed inputs, one executor exchange, evidence-derived acceptance, a queue-only terminal commit and delivery. The executor works under the installed methodology (the CLAUDE.md/AGENTS.md instruction block)
|
|
3
|
+
`drain` runs the queue serially. Each eligible task gets an owned worktree, committed inputs, one executor exchange, evidence-derived acceptance, a queue-only terminal commit and delivery. The executor works under the installed methodology (the CLAUDE.md/AGENTS.md instruction block). Formats: [package-format.md](package-format.md).
|
|
4
4
|
|
|
5
5
|
## Commands
|
|
6
6
|
|
|
@@ -11,7 +11,7 @@ python3 "$DEVLYN_SKILL_DIR/scripts/queue.py" drain --repo . [--local-only] [--wo
|
|
|
11
11
|
|
|
12
12
|
Both print one JSON object; exit 1 is `BLOCKED` with a `reason`.
|
|
13
13
|
|
|
14
|
-
- `status` takes `queue.lock` in the repository's Git directory and finishes `add`'s cleanup (step 1); it reports reconciled counts (`pending`, `active`, `accepted`, `failed`, `blocked`, `legacy_pending`), the `next` task, `blockers`, deliveries still pending with their resume (drain again until the terminal commit is attached, then `
|
|
14
|
+
- `status` takes `queue.lock` in the repository's Git directory and finishes `add`'s cleanup (step 1); it reports reconciled counts (`pending`, `active`, `accepted`, `failed`, `blocked`, `legacy_pending`), the `next` task, `blockers`, deliveries still pending with their resume (drain again until the terminal commit is attached, then the printed completion command; add `--writers-stopped` only after stopping the task's writers), and under `cleanup` the package copies `add` kept or could not remove.
|
|
15
15
|
- `drain` ends `DRAINED` (no runnable task remains; open deliveries show per task), `WAITING` (a task waits with its reason, or legacy rows need planning) or `BLOCKED` (unreadable or ambiguous authoritative queue, invalid invocation, preflight refusal or held drain lock). Progress lines `devlyn-loop: <task>: <event>` go to stderr.
|
|
16
16
|
- `--local-only` (alias `--no-push`) keeps the loop local. A manifest `delivery: local-only` and any earlier local receipt of the loop keep that restriction for later drains. A local loop needs no remote; nothing is fetched or pushed. An `auto`/`pr` loop none of whose tasks has a pushed PR runs as a local loop from its capture and the branch it was added on. Once one has, that PR is never rewritten to local and the loop is never run locally: its tasks wait, naming the PR, while other work continues.
|
|
17
17
|
- Worktrees default to `<repo parent>/<repo name>.devlyn/<loop-id>/<task-id>`.
|
|
@@ -32,7 +32,7 @@ The host supplies the executor and maps its configured engine to the argv; the l
|
|
|
32
32
|
6. **Derive acceptance** with `acceptance.py accept`. It binds the committed contract to the packet digests; requires the candidate to be the owned branch head with HEAD checked out on that branch, to descend from the inputs, to leave the package (its queue file included) and `docs/specs/queue.md` untouched and to have a clean worktree; executes each declared command on that source, recording raw stdout/stderr and exit, timeout or spawn outcomes (a runner result is reused only when it is wholly clean — no reasons, so no failed check and no source change during its checks — for the same source and contract digests, with unchanged streams); evaluates the file and diff guards against the inputs; confirms the source is unchanged; checks review records; and writes `.devlyn/loop/acceptance.json`. Missing checks, missing review coverage and open binding findings cannot produce `[x]`, and acceptance weakening is not repair. This establishes command outcomes and evidence completeness, not a reviewer's semantic judgment; deliberate evidence forgery is outside the trust model.
|
|
33
33
|
7. **Bind before terminal metadata.** `task-complete.py accept --receipt <receipt> --acceptance <worktree>/.devlyn/loop/acceptance.json` takes evidence custody and points the recovery ref at the source. Failed results are bound the same way; they never become a frontier or a publishable product.
|
|
34
34
|
8. **Commit the terminal transition** under the queue lock: one commit whose sole parent is the bound source and whose only change is this task's row in `docs/specs/<loop-id>/queue.md` (`[x]`, or `[F] — <first reason> (receipt <id>)`), validated as the only legal transition. Then `task-complete.py attach --receipt <receipt> --commit <terminal> --file docs/specs/<loop-id>/queue.md` records it separately from the source and moves the recovery ref to it.
|
|
35
|
-
9. **Deliver** with `task-complete.py complete --receipt <receipt> --writers-stopped`, plus `--local-only` or, until the receipt records a mode, the
|
|
35
|
+
9. **Deliver** after waiting for the executor, with `task-complete.py complete --receipt <receipt> --writers-stopped`, plus `--local-only` or, until the receipt records a mode, the packet's `--mode auto|pr`, so a per-task `--mode` persists. Local-only keeps source, custody and the recovery ref with no remote effect. `auto`/`pr` publish the terminal commit under the completion contract; a pending or refused delivery keeps product acceptance, resources and a resume command. Where writer cessation cannot be observed, or a process still uses the tree, a merged delivery settles (`COMPLETE`) with its workspace, task ref and custody retained and reported under workspace cleanup; retained cleanup never blocks the queue. Failed products are never published. The receipt's `delivery` field checkpoints the latest outcome. A local chain is never turned into stacked PRs; publishing it requires integrated acceptance of the actual publication candidate.
|
|
36
36
|
10. **Recover** from durable state only:
|
|
37
37
|
|
|
38
38
|
| Interrupted | Resume |
|
|
@@ -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():
|
|
@@ -1238,11 +1238,21 @@ def summary(v, row):
|
|
|
1238
1238
|
item["reason"] = reason
|
|
1239
1239
|
if receipt:
|
|
1240
1240
|
acceptance_record = receipt.get("acceptance") or {}
|
|
1241
|
-
branch, worktree = None, None
|
|
1241
|
+
branch, worktree, resume = None, None, None
|
|
1242
1242
|
try:
|
|
1243
1243
|
worktree = receipt["worktree"] if receipt.get("worktree") and Path(receipt["worktree"]).exists() else None
|
|
1244
1244
|
if receipt.get("branch") and ref_value(v["anchor"], "refs/heads/" + receipt["branch"]):
|
|
1245
1245
|
branch = receipt["branch"]
|
|
1246
|
+
if receipt.get("acceptance") and receipt.get("delivery") not in SETTLED:
|
|
1247
|
+
if receipt.get("queue"):
|
|
1248
|
+
argv = [sys.executable, str(SKILLS / "_shared/task-complete.py"), "complete", "--receipt", str(state["path"])]
|
|
1249
|
+
if receipt.get("writers_released"):
|
|
1250
|
+
argv.append("--writers-stopped")
|
|
1251
|
+
if not receipt.get("local_only") and not receipt.get("mode_override"):
|
|
1252
|
+
argv += ["--mode", read_json(state["path"].parent / "packet.json")["delivery"]]
|
|
1253
|
+
resume = shlex.join(argv)
|
|
1254
|
+
else:
|
|
1255
|
+
resume = "drain again: the terminal commit is not attached yet"
|
|
1246
1256
|
except (LoopError, OSError) as exc:
|
|
1247
1257
|
diagnostic = (f"task summary unavailable: {row['identity']} worktree {receipt.get('worktree')}, "
|
|
1248
1258
|
f"branch {receipt.get('branch')}: {exc}; repair access and drain again")
|
|
@@ -1250,9 +1260,7 @@ def summary(v, row):
|
|
|
1250
1260
|
v["cleanup"].append(diagnostic)
|
|
1251
1261
|
item.update(allocation_base=receipt.get("baseline"), **{"source" if state["kind"] == "accepted" else "candidate": receipt.get("source_sha")},
|
|
1252
1262
|
terminal=(receipt.get("queue") or {}).get("commit"), delivery=receipt.get("delivery"), pr=receipt.get("pr_url"),
|
|
1253
|
-
resume=
|
|
1254
|
-
else shlex.join([sys.executable, str(SKILLS / "_shared/task-complete.py"), "complete", "--receipt", str(state["path"])])
|
|
1255
|
-
if receipt.get("queue") else "drain again: the terminal commit is not attached yet",
|
|
1263
|
+
resume=resume,
|
|
1256
1264
|
assumptions=acceptance_record.get("assumptions", []),
|
|
1257
1265
|
questions=[r.removeprefix("needs-review: ") for r in acceptance_record.get("reasons", []) if r.startswith("needs-review: ")],
|
|
1258
1266
|
worktree=worktree,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devlyn-cli",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.13",
|
|
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": {
|