devlyn-cli 3.3.0 → 3.3.1
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
CHANGED
|
@@ -38,7 +38,7 @@ Each skill's `SKILL.md` is the source of truth for flags and workflow. Do not du
|
|
|
38
38
|
|
|
39
39
|
Engine roles: orchestrator = whichever CLI the user opened (this contract is symmetric with CLAUDE.md. If you are Codex orchestrating `/devlyn:resolve`, the phase machinery is mandatory regardless of task size; if you will not run it, say so explicitly and stop — do not silently degrade to ad-hoc execution); legacy executor (with optional separate worker/primary/pair profiles) uses the canonical skill's orchestrator-supported default, overridable per run with `--engine <name>` or durably via machine-local `.devlyn/engines.json` `{"executor": "<name>", "pair_judge_priority": ["<name>", ...]}`; PLAN is orchestrator-fixed and never inherits `--engine` or the executor pin; pair judge = first available OTHER engine, default for VERIFY whenever available; risk probes remain conditional and --no-pair opts out. Pins fail closed (`BLOCKED:<engine>-unavailable` / `BLOCKED:invalid-engine-config`); new engines plug in by shipping `_shared/adapters/<name>.md`. `/devlyn:engines` (no args) shows the role table + detected engines; `executor <name>` / `pair <name>,...` / `clear` manage the pins. The executor pin binds the orchestrator in plain conversation too, not only inside a skill run: when you would implement directly and executor is pinned to a non-default engine, route that work through the pin — via `/devlyn:resolve` (reads it at PHASE 0) or by delegating to that engine; no pin / no file → use the canonical skill's orchestrator-supported default. The pair-judge pin stays pipeline-scoped. `/devlyn:engines role` configures explicit engine/model/effort for supported worker and judge routes; `/devlyn:resolve --role-config <path>` overrides roles for one run. Canonical skills define precedence, evidence and unsupported-route errors; absent profiles preserve existing defaults.
|
|
40
40
|
|
|
41
|
-
Conversational entry: Before loading a workflow or writing files, inspect the requested change and relevant callers/tests. Quoted phase names, past logs and skill-file paths are context, not an instruction to invoke that workflow; follow the current user request. Default to direct execution when inspection makes the requested behavior, affected boundaries and verification tractable in the current context, including bounded multi-file work. Preserve requested behavior, scope, explicit constraints and the executor pin; make scoped edits, run required checks, review the final diff, and report changes with evidence. One concise route/check explanation suffices; ordinary direct work needs no spec, pipeline state, phase workers or extra approval. For direct/full/queue work, the outer owner reads `references/task-completion.md` relative to the installed `devlyn:resolve` skill's directory: prospective task-branch ownership (own linked worktree), scoped commit acceptance, then
|
|
41
|
+
Conversational entry: Before loading a workflow or writing files, inspect the requested change and relevant callers/tests. Quoted phase names, past logs and skill-file paths are context, not an instruction to invoke that workflow; follow the current user request. Default to direct execution when inspection makes the requested behavior, affected boundaries and verification tractable in the current context, including bounded multi-file work. Preserve requested behavior, scope, explicit constraints and the executor pin; make scoped edits, run required checks, review the final diff, and report changes with evidence. One concise route/check explanation suffices; ordinary direct work needs no spec, pipeline state, phase workers or extra approval. For direct/full/queue work, the outer owner reads `references/task-completion.md` relative to the installed `devlyn:resolve` skill's directory: prospective task-branch ownership (own linked worktree), scoped commit acceptance, then PR/merge and recoverable owned-resource cleanup after required archive/queue commits. Local-only/no-push wins; delivery pending/failure stays separate from product verification.
|
|
42
42
|
|
|
43
43
|
Use full `/devlyn:resolve` automatically only when inspection identifies interacting requirements or verification work too complex to manage reliably in the current context, such as coupled durable state, concurrent ownership and failure recovery across boundaries. Name the concrete interaction or verification gap. Domain labels (security/auth/payment/persistence/concurrency/API), file count and a spec document alone do not trigger it. Investigate or clarify missing intent/access first; a pipeline cannot supply them. Direct work still requires risk-proportionate checks and independent review for consequential changes. Explicit resolve (even small), explicit spec-mode workflows and queue drains retain all canonical phases, independent verification, pins and failure handling. If the full-route condition emerges mid-edit, carry the existing delta into its scope/evidence; do not reset the baseline or reconfirm existing authorization.
|
|
44
44
|
|
package/CLAUDE.md
CHANGED
|
@@ -51,7 +51,7 @@ Each skill's `SKILL.md` is the source of truth for its flags and workflow — do
|
|
|
51
51
|
|
|
52
52
|
### Conversational entry + full-route handoff
|
|
53
53
|
|
|
54
|
-
Before loading a workflow or writing files, inspect the requested change and relevant callers/tests. Quoted phase names, past logs and skill-file paths are context, not an instruction to invoke that workflow; follow the current user request. Default to direct execution when inspection makes the requested behavior, affected boundaries and verification tractable in the current context, including bounded multi-file work. Preserve requested behavior, scope, explicit constraints and the executor pin; make scoped edits, run required checks, review the final diff, and report changes with evidence. One concise route/check explanation suffices; ordinary direct work needs no spec, pipeline state, phase workers or extra approval. For direct/full/queue work, the outer owner reads `references/task-completion.md` relative to the installed `devlyn:resolve` skill's directory: prospective task-branch ownership (own linked worktree), scoped commit acceptance, then
|
|
54
|
+
Before loading a workflow or writing files, inspect the requested change and relevant callers/tests. Quoted phase names, past logs and skill-file paths are context, not an instruction to invoke that workflow; follow the current user request. Default to direct execution when inspection makes the requested behavior, affected boundaries and verification tractable in the current context, including bounded multi-file work. Preserve requested behavior, scope, explicit constraints and the executor pin; make scoped edits, run required checks, review the final diff, and report changes with evidence. One concise route/check explanation suffices; ordinary direct work needs no spec, pipeline state, phase workers or extra approval. For direct/full/queue work, the outer owner reads `references/task-completion.md` relative to the installed `devlyn:resolve` skill's directory: prospective task-branch ownership (own linked worktree), scoped commit acceptance, then PR/merge and recoverable owned-resource cleanup after required archive/queue commits. Local-only/no-push wins; delivery pending/failure stays separate from product verification.
|
|
55
55
|
|
|
56
56
|
Use full `/devlyn:resolve` automatically only when inspection identifies interacting requirements or verification work too complex to manage reliably in the current context, such as coupled durable state, concurrent ownership and failure recovery across boundaries. Name the concrete interaction or verification gap. Domain labels (security/auth/payment/persistence/concurrency/API), file count and a spec document alone do not trigger it. Investigate or clarify missing intent/access first; a pipeline cannot supply them. Direct work still requires risk-proportionate checks and independent review for consequential changes. Explicit resolve (even small), explicit spec-mode workflows and queue drains retain all canonical phases, independent verification, pins and failure handling. If the full-route condition emerges mid-edit, carry the existing delta into its scope/evidence; do not reset the baseline or reconfirm existing authorization.
|
|
57
57
|
|
package/README.md
CHANGED
|
@@ -84,10 +84,10 @@ PLAN → IMPLEMENT → BUILD_GATE → CLEANUP → VERIFY (fresh subagent
|
|
|
84
84
|
Common flags: `--engine claude|codex|omp` (default: the orchestrator-supported default), `--role-config <path>` (one-run worker/judge profiles, see below), `--bypass build-gate,cleanup`, `--pair-verify` (force pair-mode JUDGE in VERIFY), `--no-pair` (intentional solo VERIFY), `--risk-probes` / `--no-risk-probes`, `--perf` (per-phase timing).
|
|
85
85
|
`--pair-verify` and `--no-pair` are mutually exclusive; using both stops with `BLOCKED:invalid-flags`.
|
|
86
86
|
|
|
87
|
-
Each task gets its own linked worktree; accepted tasks default to scoped commit → push → PR
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
Each task gets its own linked worktree; accepted tasks default to scoped commit → push → PR
|
|
88
|
+
→ merge when the repository allows it (otherwise the PR waits for a person). After the
|
|
89
|
+
PR merges, the worktree and branch its session released are cleaned; anything in use is
|
|
90
|
+
kept. Set `git config --local devlyn.completionMode pr` to stop at the PR; `task-complete.py complete --mode auto|pr`
|
|
91
91
|
overrides one task. Local-only/no-push instructions take precedence; existing
|
|
92
92
|
branches cannot be adopted. Full runs require successful archive before delivery;
|
|
93
93
|
direct tasks use their actual checks and root acceptance. Verify-only never
|
|
@@ -222,6 +222,7 @@
|
|
|
222
222
|
"9f62c5b2a94e8b5e81528ccbdb6f78d147d14882108568b2cb3b7f85212694c0",
|
|
223
223
|
"a02eb2d8d77553327fb77377dbfedeab059b2e68e0a33c469b9be54a5d55182b",
|
|
224
224
|
"a3a15249968f64085b0cec8c0826688a4c3a1e4cf09b4879054baa086d5cbca1",
|
|
225
|
+
"a4da26a46ab83815d8ca5d747c68a907ad67dad485adbc6c863b982ee23b307e",
|
|
225
226
|
"a5d6ff585d696254d6727cd1e6d7f8825881f90210eff744265e211ced715ea3",
|
|
226
227
|
"a7ff913df40945a8a16b6632214020a368119ff665953d4a07ec58330b39f101",
|
|
227
228
|
"aafc680a7ac73e100f4a17d2c4070190e68563f78614c39f4c2f58f1e291d9a9",
|
|
@@ -775,6 +776,7 @@
|
|
|
775
776
|
"e29fec7d54a144ea4867a3764fccdee6849d58d0f32eacdd56b2c317863fa1c6",
|
|
776
777
|
"e2e0fc585321ee2013cccaed95d47c49fbca71b54c67abad9080d19d70e3c1a5",
|
|
777
778
|
"e3b2f7be15a793f07cbe8e51aa2feeac51d4c67077b94e5e20bad2e14be07fc5",
|
|
779
|
+
"e468c242cc38c390f9679fca61f355848f58d51570249dfbb65b4f2e26a9dfb1",
|
|
778
780
|
"e4bafa0f553e899b65f05feabf4cbe86d8246c57cbba745eb79e6aa858f40a6b",
|
|
779
781
|
"e5160f4da789914e7f93694a55b9f80b8d06c3b0a126ff60f7627b0b418ae292",
|
|
780
782
|
"e55a62fa1a993f5df1b44d1c55ef27915938d58a1c357604f3dc150f795672df",
|
|
@@ -172,7 +172,7 @@ def policy(receipt, override):
|
|
|
172
172
|
require(config.returncode == 1 or config.stdout in {"auto\n", "pr\n"}, "invalid local devlyn.completionMode; use auto|pr")
|
|
173
173
|
value = config.stdout.strip() if config.returncode == 0 else None
|
|
174
174
|
require(override is None or override in {"auto", "pr"}, "invalid task mode; use auto|pr")
|
|
175
|
-
return override or value or "
|
|
175
|
+
return override or value or "auto"
|
|
176
176
|
|
|
177
177
|
|
|
178
178
|
def allocate(args):
|
|
@@ -685,8 +685,6 @@ def complete(args):
|
|
|
685
685
|
inspect_workspace(current)
|
|
686
686
|
bind_acceptance(receipt, path, args.acceptance)
|
|
687
687
|
info = repo_policy(receipt)
|
|
688
|
-
if mode == "auto":
|
|
689
|
-
require(info["mergeCommitAllowed"], "repository disallows merge commits; use pr mode or resolve policy with repository owner")
|
|
690
688
|
if receipt.get("pr_number"):
|
|
691
689
|
pr = json.loads(gh(receipt, "pr", "view", str(receipt["pr_number"]), "--json", PR_FIELDS))
|
|
692
690
|
else:
|
|
@@ -725,12 +723,18 @@ def complete(args):
|
|
|
725
723
|
inspect_workspace(receipt)
|
|
726
724
|
verify_files(Path(receipt["worktree"]), receipt["files"])
|
|
727
725
|
require(remote_head(receipt, receipt["branch"]) == receipt["publish_sha"], "remote head race before merge")
|
|
726
|
+
refused = None
|
|
728
727
|
if not pr.get("autoMergeRequest"):
|
|
729
|
-
|
|
728
|
+
try:
|
|
729
|
+
require(info["mergeCommitAllowed"], "repository disallows merge commits")
|
|
730
|
+
gh(receipt, "pr", "merge", str(pr["number"]), "--auto", "--merge", "--match-head-commit", receipt["publish_sha"])
|
|
731
|
+
except CompletionError as error:
|
|
732
|
+
refused = str(error)
|
|
730
733
|
pr = json.loads(gh(receipt, "pr", "view", str(pr["number"]), "--json", PR_FIELDS))
|
|
731
734
|
validate_pr(receipt, pr)
|
|
732
735
|
if pr["state"] != "MERGED":
|
|
733
|
-
|
|
736
|
+
# Merge only when the repository allows it; a refused request leaves the PR for a person.
|
|
737
|
+
return dict(result("PR"), merge_refused=refused) if refused and not pr.get("autoMergeRequest") else result("PENDING")
|
|
734
738
|
receipt["merge"] = pr
|
|
735
739
|
atomic_json(path, receipt)
|
|
736
740
|
cleanup(receipt, path, pr)
|
|
@@ -1242,7 +1246,7 @@ class CompletionTests(unittest.TestCase):
|
|
|
1242
1246
|
self.g("config", "url." + alias + ".insteadOf", intended)
|
|
1243
1247
|
self.allocate(); self.accept()
|
|
1244
1248
|
self.assertEqual(json.loads(self.receipt.read_text(encoding="utf-8"))["remote_url"], {"literal": intended, "fetch": alias, "push": alias})
|
|
1245
|
-
result, _ = self.complete()
|
|
1249
|
+
result, _ = self.complete("--mode", "pr")
|
|
1246
1250
|
self.assertEqual(result["status"], "PR")
|
|
1247
1251
|
self.assertEqual(self.g("ls-remote", "origin", "refs/heads/task/fixture").split()[0], self.sha)
|
|
1248
1252
|
d = json.loads(self.data.read_text(encoding="utf-8"))
|
|
@@ -1374,6 +1378,15 @@ class CompletionTests(unittest.TestCase):
|
|
|
1374
1378
|
self.assertEqual(result["status"], "COMPLETE")
|
|
1375
1379
|
self.assertFalse(self.task.exists())
|
|
1376
1380
|
|
|
1381
|
+
@unittest.skipUnless(sys.platform == "darwin" or sys.platform.startswith("linux"), "writer observation requires POSIX")
|
|
1382
|
+
def test_default_mode_merges_when_possible_and_cleans(self):
|
|
1383
|
+
self.allocate(); self.accept()
|
|
1384
|
+
result, _ = self.complete("--writers-stopped")
|
|
1385
|
+
self.assertEqual(result["status"], "COMPLETE")
|
|
1386
|
+
self.assertEqual(json.loads(self.data.read_text(encoding="utf-8")).get("merges"), 1)
|
|
1387
|
+
self.assertFalse(self.task.exists())
|
|
1388
|
+
self.assertEqual(self.g("branch", "--list", "task/fixture"), "")
|
|
1389
|
+
|
|
1377
1390
|
def test_release_is_refused_from_inside_the_task_tree(self):
|
|
1378
1391
|
self.allocate(); self.accept()
|
|
1379
1392
|
_, r = self.complete("--mode", "pr", "--writers-stopped", cwd=self.task, success=False)
|
|
@@ -1710,10 +1723,12 @@ class CompletionTests(unittest.TestCase):
|
|
|
1710
1723
|
|
|
1711
1724
|
def test_interrupted_external_effects(self):
|
|
1712
1725
|
self.allocate(); self.accept()
|
|
1713
|
-
for effect in ("push", "create"
|
|
1726
|
+
for effect in ("push", "create"):
|
|
1714
1727
|
self.configure(**{"interrupt_"+effect: True})
|
|
1715
1728
|
_, r = self.complete("--writers-stopped", "--mode", "auto", success=False)
|
|
1716
1729
|
self.assertNotEqual(r.returncode, 0)
|
|
1730
|
+
# A merge command that fails after merging is judged by the PR state, not its exit code.
|
|
1731
|
+
self.configure(interrupt_merge=True)
|
|
1717
1732
|
result, _ = self.complete("--writers-stopped")
|
|
1718
1733
|
self.assertEqual(result["status"], "COMPLETE")
|
|
1719
1734
|
d = json.loads(self.data.read_text(encoding="utf-8"))
|
|
@@ -1801,16 +1816,19 @@ class CompletionTests(unittest.TestCase):
|
|
|
1801
1816
|
def test_policy_and_local_only_persist(self):
|
|
1802
1817
|
self.allocate(); self.accept()
|
|
1803
1818
|
self.configure(merge_allowed=False)
|
|
1804
|
-
|
|
1805
|
-
self.
|
|
1806
|
-
|
|
1819
|
+
result, _ = self.complete()
|
|
1820
|
+
self.assertEqual((result["status"], result["merge_refused"]), ("PR", "repository disallows merge commits"))
|
|
1821
|
+
d = json.loads(self.data.read_text(encoding="utf-8"))
|
|
1822
|
+
self.assertEqual((d.get("pushs", 0), d.get("merges", 0)), (1, 0))
|
|
1807
1823
|
self.complete("--mode", "pr")
|
|
1808
1824
|
result, _ = self.complete()
|
|
1809
1825
|
self.assertEqual(result["status"], "PR")
|
|
1826
|
+
self.assertNotIn("merge_refused", result)
|
|
1810
1827
|
self.configure(merge_allowed=True, auto_allowed=False, pending=True)
|
|
1811
|
-
|
|
1812
|
-
self.
|
|
1813
|
-
self.assertIn("disallows auto merge",
|
|
1828
|
+
result, _ = self.complete("--mode", "auto")
|
|
1829
|
+
self.assertEqual(result["status"], "PR")
|
|
1830
|
+
self.assertIn("disallows auto merge", result["merge_refused"])
|
|
1831
|
+
self.assertTrue(self.task.exists())
|
|
1814
1832
|
result, _ = self.complete("--no-push")
|
|
1815
1833
|
self.assertEqual(result["status"], "LOCAL_ONLY")
|
|
1816
1834
|
result, _ = self.complete(acceptance=False)
|
|
@@ -101,19 +101,19 @@ python3 "$DEVLYN_SHARED_DIR/task-complete.py" complete \
|
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
Project policy is `git config --local devlyn.completionMode auto|pr`; absent means
|
|
104
|
-
`
|
|
104
|
+
`auto`. Invalid local values fail before external effects, even with an override.
|
|
105
105
|
Use `complete --mode auto|pr` for one task, or `--local-only`/`--no-push` to honor
|
|
106
106
|
the user's delivery restriction. These per-task choices persist in the receipt.
|
|
107
107
|
|
|
108
108
|
`pr` pushes the exact accepted task ref and creates/reuses its exact repository,
|
|
109
109
|
head and base PR. While OPEN, it cancels owned auto-merge, reports `PR` with its URL
|
|
110
110
|
and retains the checkout. Once MERGED by any method, either mode cleans released
|
|
111
|
-
resources in that call, on resume or at the next allocation.
|
|
111
|
+
resources in that call, on resume or at the next allocation. Default `auto` also requests
|
|
112
112
|
`gh pr merge --auto --merge --match-head-commit <accepted SHA>`. Repository
|
|
113
113
|
checks/reviews and merge policy remain authoritative: no admin bypass, strategy
|
|
114
|
-
fallback or repository setting changes.
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
fallback or repository setting changes. When the repository disallows merge
|
|
115
|
+
commits or refuses the request, delivery reports `PR` with `merge_refused` and
|
|
116
|
+
the PR waits for a person. Command success alone never proves merge.
|
|
117
117
|
|
|
118
118
|
`PENDING` retains the workspace and reports a receipt-based resume command.
|
|
119
119
|
`BLOCKED` includes the cause and the same retry path; repair the reported condition
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devlyn-cli",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.1",
|
|
4
4
|
"description": "Loop & harness engineering for AI coding agents (Claude Code, Codex, and any AGENTS.md-driven agent) — take intent to shipped, engineer-quality software hands-free with phase-gated pipelines and agent orchestration",
|
|
5
5
|
"homepage": "https://github.com/fysoul17/devlyn-cli#readme",
|
|
6
6
|
"bin": {
|