devlyn-cli 4.2.4 → 4.2.5
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/devlyn.js +1 -0
- package/bin/skill-history.json +5 -3
- package/config/skills/_shared/role-config.py +3 -1
- package/config/skills/_shared/task-complete.py +14 -3
- package/config/skills/devlyn-ideate/SKILL.md +2 -0
- package/config/skills/devlyn-ideate/references/loop.md +1 -1
- package/config/skills/devlyn-ideate/scripts/queue.py +10 -3
- package/package.json +1 -1
package/bin/devlyn.js
CHANGED
|
@@ -880,6 +880,7 @@ function install(targets, global, optionalNames = []) {
|
|
|
880
880
|
else installClaudeCore();
|
|
881
881
|
}
|
|
882
882
|
log(`\n✅ devlyn ${PKG.version} installed`, 'green');
|
|
883
|
+
if (global) log('The principles block installs per project: run npx devlyn-cli in the project without --global.');
|
|
883
884
|
noticeGlobalDrift(roots);
|
|
884
885
|
return roots;
|
|
885
886
|
}
|
package/bin/skill-history.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"source": "devlyn-cli@4.1.0 (npm tarball sha1 398ff07d3d689b56cc22e18ac50260bc6e4e9e85); devlyn-cli@4.2.0 (npm tarball sha1 c93c2fb1cf5630b18d2eba0085182c14fddbe2d5); devlyn-cli@4.2.4 (_shared and devlyn-ideate, fingerprinted from an install of
|
|
2
|
+
"source": "devlyn-cli@4.1.0 (npm tarball sha1 398ff07d3d689b56cc22e18ac50260bc6e4e9e85); devlyn-cli@4.2.0 (npm tarball sha1 c93c2fb1cf5630b18d2eba0085182c14fddbe2d5); devlyn-cli@4.2.4 and 4.2.5 (_shared and devlyn-ideate, fingerprinted from an install of each packed tarball); whole-tree fingerprints from bin/skill-ownership.js; deprecated commands: commands/ and optional-commands/*/ files of devlyn-cli 0.0.1-0.5.8 (every published version that shipped them), LF-normalized SHA-256",
|
|
3
3
|
"skills": {
|
|
4
4
|
"_shared": [
|
|
5
5
|
"eb3327d5e6940bd5e0459fa28c9ab323d9c58ccfdeb9426c19cd027cc4e5c4ed",
|
|
6
6
|
"8c80926ab03b2477e52264495b35c2116f65fc51b02e8c56e441b5705872392a",
|
|
7
|
-
"3c81c575cc43d70488865b440204cb399091405d9b7f81dbf047a32d3d5ac045"
|
|
7
|
+
"3c81c575cc43d70488865b440204cb399091405d9b7f81dbf047a32d3d5ac045",
|
|
8
|
+
"6546d6b833701f2641a004802cfc17ec62442e5d0003f396cb0231b676ba9a4a"
|
|
8
9
|
],
|
|
9
10
|
"devlyn-design-ui": [
|
|
10
11
|
"91080266232fb62b6f1d45932184f065589e12eb66f02fb6442527c6dfcd81b6"
|
|
@@ -16,7 +17,8 @@
|
|
|
16
17
|
"devlyn-ideate": [
|
|
17
18
|
"8ff5148cc65b4608c35b29afd6fef20d42d8da93a094144d98cfc63e74089b6e",
|
|
18
19
|
"5824f07e455fe1c3942bf595a12e935258d4c0a851ca1bf741db915603d1c4e0",
|
|
19
|
-
"7f451e48e391643b34f6249244bc47d93d6303875e7b092771e3af0718343bc1"
|
|
20
|
+
"7f451e48e391643b34f6249244bc47d93d6303875e7b092771e3af0718343bc1",
|
|
21
|
+
"d6433bf0f96dd9dd485c3623885255738654674b3e23cfcc1a2cfd24f3cda96e"
|
|
20
22
|
],
|
|
21
23
|
"devlyn-queue": [
|
|
22
24
|
"4c7d988dcd154c7bf658d2d24cd7868955df9d854ed87eb784742d6d4bb229e0"
|
|
@@ -102,7 +102,8 @@ def resolve(work, default_engine, *, available=None, shared=SHARED):
|
|
|
102
102
|
"""The project's executor pin, else the invoking CLI; status only, never a dispatch decision."""
|
|
103
103
|
config, source = read_config(Path(work) / ".devlyn/engines.json", optional=True, shared=shared)
|
|
104
104
|
engine = config.get("executor", default_engine)
|
|
105
|
-
adapter
|
|
105
|
+
# read_config validates pins; the invoking host needs no adapter to report its own default.
|
|
106
|
+
name(engine, "engine")
|
|
106
107
|
available = available or (lambda candidate: shutil.which(candidate) is not None)
|
|
107
108
|
return {"executor": {"engine": engine, "source": "engines.json" if "executor" in config else "default",
|
|
108
109
|
"availability": "CLI-present/auth-unchecked" if available(engine) else "CLI-unavailable"},
|
|
@@ -113,6 +114,7 @@ def select(work, default_engine, *, available=None, shared=SHARED):
|
|
|
113
114
|
"""The executor for dispatch. A pin is a promise: an unavailable pinned engine is never substituted."""
|
|
114
115
|
result = resolve(work, default_engine, available=available, shared=shared)
|
|
115
116
|
executor = result["executor"]
|
|
117
|
+
adapter(executor["engine"], shared=shared)
|
|
116
118
|
if executor["source"] == "engines.json" and executor["availability"] == "CLI-unavailable":
|
|
117
119
|
fail(f"pinned executor {executor['engine']} is unavailable; install and authenticate its CLI, "
|
|
118
120
|
f"verify `{executor['engine']} --version`, and retry, or pin an available engine with `devlyn-engines executor <name>`",
|
|
@@ -375,9 +375,14 @@ def gh(receipt, *args):
|
|
|
375
375
|
return command(["gh", *args, "--repo", "github.com/"+receipt["repository"]])
|
|
376
376
|
|
|
377
377
|
|
|
378
|
+
def repository_info(repository):
|
|
379
|
+
info = json.loads(command(["gh", "repo", "view", "github.com/"+repository, "--json", "nameWithOwner,url,defaultBranchRef,mergeCommitAllowed"]))
|
|
380
|
+
require(info["nameWithOwner"].lower() == repository.lower() and info["url"].lower() == "https://github.com/"+repository.lower(), "GitHub repository identity changed")
|
|
381
|
+
return info
|
|
382
|
+
|
|
383
|
+
|
|
378
384
|
def repo_policy(receipt):
|
|
379
|
-
info =
|
|
380
|
-
require(info["nameWithOwner"].lower() == receipt["repository"].lower() and info["url"].lower() == "https://github.com/"+receipt["repository"].lower(), "GitHub repository identity changed")
|
|
385
|
+
info = repository_info(receipt["repository"])
|
|
381
386
|
require(info["defaultBranchRef"]["name"] == receipt["base"] and receipt["branch"] != info["defaultBranchRef"]["name"], "base/default branch changed; retain resources")
|
|
382
387
|
return info
|
|
383
388
|
|
|
@@ -799,7 +804,7 @@ def main():
|
|
|
799
804
|
attachment = actions.add_parser("attach")
|
|
800
805
|
attachment.add_argument("--receipt", required=True)
|
|
801
806
|
attachment.add_argument("--commit", required=True)
|
|
802
|
-
attachment.add_argument("--file",
|
|
807
|
+
attachment.add_argument("--file", required=True)
|
|
803
808
|
completion = actions.add_parser("complete")
|
|
804
809
|
completion.add_argument("--receipt", required=True)
|
|
805
810
|
completion.add_argument("--acceptance")
|
|
@@ -2141,6 +2146,12 @@ class CompletionTests(unittest.TestCase):
|
|
|
2141
2146
|
result, _ = self.cli("accept", "--receipt", self.receipt, "--acceptance", acceptance, success=False)
|
|
2142
2147
|
self.assertEqual(result["status"], "FAILED" if verdict == "FAILED" else "BLOCKED", result)
|
|
2143
2148
|
|
|
2149
|
+
def test_attach_requires_an_explicit_queue_file(self):
|
|
2150
|
+
result = self.run_cmd([sys.executable, str(Path(__file__).resolve()), 'attach',
|
|
2151
|
+
'--receipt', str(self.root / 'absent.json'), '--commit', 'HEAD'], success=False)
|
|
2152
|
+
self.assertEqual(result.returncode, 2, result.stdout + result.stderr)
|
|
2153
|
+
self.assertIn('the following arguments are required: --file', result.stderr)
|
|
2154
|
+
|
|
2144
2155
|
def test_attach_requires_the_terminal_checkout_only_for_a_publishable_result(self):
|
|
2145
2156
|
for verdict in ("ACCEPTED", "FAILED"):
|
|
2146
2157
|
with self.subTest(verdict=verdict):
|
|
@@ -100,6 +100,8 @@ Run `python3 "$DEVLYN_SKILL_DIR/scripts/queue.py" status --repo .` and report it
|
|
|
100
100
|
|
|
101
101
|
## drain
|
|
102
102
|
|
|
103
|
+
`status`, `add` and `drain` write the repository's Git directory, and auto/pr delivery needs network access, so a sandboxed host runs them with escalation or full access.
|
|
104
|
+
|
|
103
105
|
Follow [loop.md](references/loop.md). The executor is the configured route, the `.devlyn/engines.json` `executor` pin or else this CLI, checked per `_shared/engine-preflight.md`. Pass its argv after `--`. `<git dir>` is the output of `git rev-parse --path-format=absolute --git-common-dir`, where the executor commits and writes its submission; the drain fills `{worktree_git_dir}` with the task worktree's own Git directory, which Codex's sandbox keeps read-only unless that exact directory is listed:
|
|
104
106
|
|
|
105
107
|
| Executor | argv |
|
|
@@ -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`
|
|
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 `task-complete.py complete --receipt <receipt>`), 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` (conflict, invalid input or a recovery blocker). 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>`.
|
|
@@ -843,9 +843,9 @@ def evidence_ignored(anchor, common, start):
|
|
|
843
843
|
|
|
844
844
|
|
|
845
845
|
def allocate(v, row, opts):
|
|
846
|
-
"""Allocate the task's owned worktree, or return why it waits: origin names no GitHub repository,
|
|
847
|
-
remote base holds another version of the loop's package or lacks the checkout's instructions
|
|
848
|
-
start before selection)."""
|
|
846
|
+
"""Allocate the task's owned worktree, or return why it waits: origin names no GitHub repository, its base is not the
|
|
847
|
+
default, or its refreshed remote base holds another version of the loop's package or lacks the checkout's instructions
|
|
848
|
+
(waiting() checks a local start before selection)."""
|
|
849
849
|
identity, loop, task = row["identity"], row["loop"], row["task"]
|
|
850
850
|
package = v["packages"][loop]
|
|
851
851
|
manifest = package["manifest"]
|
|
@@ -868,6 +868,13 @@ def allocate(v, row, opts):
|
|
|
868
868
|
except helper["CompletionError"]:
|
|
869
869
|
return (f"{manifest['delivery']} delivery needs an origin remote of the form https://github.com/<owner>/<repo>, "
|
|
870
870
|
"git@github.com:<owner>/<repo> or ssh://git@github.com/<owner>/<repo>; set one, or drain with --local-only")
|
|
871
|
+
try:
|
|
872
|
+
default = helper["repository_info"](values["repository"])["defaultBranchRef"]["name"]
|
|
873
|
+
except (helper["CompletionError"], OSError, KeyError, TypeError, ValueError) as exc:
|
|
874
|
+
return f"cannot check delivery repository: {exc}"
|
|
875
|
+
if manifest["base_ref"] != default:
|
|
876
|
+
return (f"{manifest['delivery']} delivery base {manifest['base_ref']} is not the repository's default branch {default}; "
|
|
877
|
+
f"plan the loop on {default}, or drain with --local-only")
|
|
871
878
|
try:
|
|
872
879
|
start = helper["remote_base"]({"common_gitdir": str(common), "remote": "origin", "base": manifest["base_ref"]})
|
|
873
880
|
except helper["CompletionError"] as exc:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devlyn-cli",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.5",
|
|
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": {
|