devlyn-cli 4.3.31 → 4.3.33
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/README.md +4 -2
- package/bin/devlyn.js +109 -22
- package/bin/harness-activation.js +4 -1
- package/bin/harness-status.js +15 -8
- package/bin/skill-history.json +10 -0
- package/config/skills/_shared/bring-in.py +10 -0
- package/config/skills/_shared/task-complete.py +787 -87
- package/config/skills/_shared/task-completion.md +6 -6
- package/config/skills/_shared/warm-build-dirs.py +13 -0
- package/config/skills/devlyn-ideate/SKILL.md +3 -2
- package/config/skills/devlyn-ideate/references/loop.md +2 -2
- package/config/skills/devlyn-ideate/scripts/acceptance.py +11 -0
- package/config/skills/devlyn-ideate/scripts/queue.py +685 -10
- package/native/claude/.claude-plugin/plugin.json +1 -1
- package/native/claude/skills/_shared/bring-in.py +10 -0
- package/native/claude/skills/_shared/task-complete.py +787 -87
- package/native/claude/skills/_shared/task-completion.md +6 -6
- package/native/claude/skills/_shared/warm-build-dirs.py +13 -0
- package/native/claude/skills/devlyn-ideate/SKILL.md +3 -2
- package/native/claude/skills/devlyn-ideate/references/loop.md +2 -2
- package/native/claude/skills/devlyn-ideate/scripts/acceptance.py +11 -0
- package/native/claude/skills/devlyn-ideate/scripts/queue.py +685 -10
- package/native/codex/.agents/plugins/marketplace.json +1 -1
- package/native/codex/plugins/devlyn/.codex-plugin/plugin.json +1 -1
- package/native/codex/plugins/devlyn/skills/_shared/bring-in.py +10 -0
- package/native/codex/plugins/devlyn/skills/_shared/task-complete.py +787 -87
- package/native/codex/plugins/devlyn/skills/_shared/task-completion.md +6 -6
- package/native/codex/plugins/devlyn/skills/_shared/warm-build-dirs.py +13 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/SKILL.md +3 -2
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/loop.md +2 -2
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/scripts/acceptance.py +11 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/scripts/queue.py +685 -10
- package/package.json +1 -1
- package/release.json +35 -35
|
@@ -201,14 +201,14 @@ publication mode pushes no task commit and opens no PR. Delivery is `COMPLETE` w
|
|
|
201
201
|
`pr` pushes the exact accepted task ref and creates/reuses its exact repository,
|
|
202
202
|
head and base PR. While OPEN, it cancels owned auto-merge, reports `PR` with its URL
|
|
203
203
|
and retains the checkout. Once MERGED by any method, either mode cleans released
|
|
204
|
-
resources in that call, on resume or at the next allocation. Default `auto` also
|
|
205
|
-
|
|
206
|
-
checks/reviews and merge policy remain authoritative: no admin bypass, strategy
|
|
204
|
+
resources in that call, on resume or at the next allocation. Default `auto` also
|
|
205
|
+
merges the accepted commit. Merge policy remains authoritative: no admin bypass, strategy
|
|
207
206
|
fallback or repository setting changes. When the repository disallows merge
|
|
208
|
-
commits or refuses the
|
|
207
|
+
commits or refuses the merge, delivery reports `PR` with `merge_refused` and
|
|
209
208
|
the PR waits for a person. Command success alone never proves merge.
|
|
210
209
|
|
|
211
|
-
`PENDING` retains the workspace
|
|
210
|
+
`PENDING` retains the workspace; its receipt-based resume command merges once
|
|
211
|
+
required checks or reviews pass, or a person merges it.
|
|
212
212
|
`BLOCKED` names the cause and retry action; repair that condition before retrying.
|
|
213
213
|
Resume reobserves Git/PR state under a per-receipt lock, reuses the original
|
|
214
214
|
acceptance and performs only missing eligible effects.
|
|
@@ -236,7 +236,7 @@ affected resources. Use `git worktree lock` to keep a tree.
|
|
|
236
236
|
A VM that shared the tree (Docker Desktop after a bind mount) can keep files open
|
|
237
237
|
until it restarts; the helper names it when identifiable and retries cleanup on
|
|
238
238
|
the next allocation or resume after the restart.
|
|
239
|
-
On native Windows,
|
|
239
|
+
On native Windows, a tree a program has a file open in, or runs in, is kept until that program lets go; one a program only watches may be removed. A managed tree instead waits for removal by hand, as its reason says; scratch that holds files is retained. Do not kill unknown processes or force worktree removal to make completion pass.
|
|
240
240
|
|
|
241
241
|
The receipt directory holds byte-verified `custody/`, `manifest.json` and a guarded
|
|
242
242
|
`refs/devlyn/completed/<id>` recovery ref. Check evidence is copied there
|
|
@@ -706,9 +706,14 @@ class WarmTests(unittest.TestCase):
|
|
|
706
706
|
(bin_dir / "cargo").write_text(FAKE_CARGO)
|
|
707
707
|
(bin_dir / "cargo").chmod(0o755)
|
|
708
708
|
self.fixture, self.log = self.root / "fixture.json", self.root / "cargo.log"
|
|
709
|
+
# A commit's detached automatic maintenance would still write into .git while a test reads the tree. Appended to
|
|
710
|
+
# the inherited GIT_CONFIG_COUNT entries, which this environment keeps.
|
|
711
|
+
count = int(os.environ.get("GIT_CONFIG_COUNT") or 0)
|
|
709
712
|
self.environment = mock.patch.dict(os.environ, {
|
|
710
713
|
"PATH": f"{bin_dir}{os.pathsep}{os.environ['PATH']}", "FAKE_CARGO": str(self.fixture),
|
|
711
714
|
"FAKE_CARGO_LOG": str(self.log), "GIT_CONFIG_GLOBAL": os.devnull, "GIT_CONFIG_NOSYSTEM": "1",
|
|
715
|
+
"GIT_CONFIG_COUNT": str(count + 1), f"GIT_CONFIG_KEY_{count}": "maintenance.auto",
|
|
716
|
+
f"GIT_CONFIG_VALUE_{count}": "false",
|
|
712
717
|
"PYTHONPATH": str(self.root) + os.pathsep + os.environ.get("PYTHONPATH", ""),
|
|
713
718
|
"PYTHONDONTWRITEBYTECODE": "1",
|
|
714
719
|
"GIT_AUTHOR_NAME": "t", "GIT_AUTHOR_EMAIL": "t@t", "GIT_COMMITTER_NAME": "t", "GIT_COMMITTER_EMAIL": "t@t"})
|
|
@@ -749,6 +754,14 @@ class WarmTests(unittest.TestCase):
|
|
|
749
754
|
def git(self, work, *args):
|
|
750
755
|
return subprocess.run(["git", "-C", str(work), *args], check=True, capture_output=True, text=True).stdout
|
|
751
756
|
|
|
757
|
+
def test_fixture_commit_starts_no_automatic_maintenance(self):
|
|
758
|
+
# Its detached child would still write into .git while a test reads the tree or cleanup removes it.
|
|
759
|
+
trace = self.root / "trace.json"
|
|
760
|
+
os.environ["GIT_TRACE2_EVENT"] = str(trace) # setUp's patch restores the environment
|
|
761
|
+
self.git(self.anchor, "commit", "-q", "--allow-empty", "-m", "traced")
|
|
762
|
+
children = [line for line in trace.read_text(encoding="utf-8").splitlines() if '"event":"child_start"' in line]
|
|
763
|
+
self.assertEqual(children, [])
|
|
764
|
+
|
|
752
765
|
def write_fixture(self, **extra):
|
|
753
766
|
packages = [
|
|
754
767
|
{"name": "app", "source": None, "manifest_path": str(self.work / "Cargo.toml"), "targets": [{"kind": ["lib"], "name": "LIB"}]},
|
|
@@ -59,6 +59,7 @@ Verify DEVLYN_SKILL_DIR/scripts/queue.py before running it. In omp, use `printf
|
|
|
59
59
|
| `plan <intent or absolute document path>` | Inspect, elicit what is necessary and write a validated loop package. Does not enqueue or execute. |
|
|
60
60
|
| `add <intent or absolute package path>` | Plan when necessary, then capture the package and queue its tasks in dependency order. Does not execute. |
|
|
61
61
|
| `status` | Reconciled pending, active, accepted and failed counts; the next runnable task; delivery and recovery blockers. |
|
|
62
|
+
| `withdraw <loop-id>` | `queue.py withdraw --repo . <loop-id>` takes back a queued loop none of whose tasks has started and that replaced no legacy row. |
|
|
62
63
|
| `drain` | Resume or drain the queue serially under existing execution authorization. |
|
|
63
64
|
| Bare intent | Plan; when the request authorizes implementation, continue through add and drain without reconfirmation. |
|
|
64
65
|
| No arguments | Status. |
|
|
@@ -104,7 +105,7 @@ For read-only consumers, `python3 "$DEVLYN_SKILL_DIR/scripts/queue.py" inspect -
|
|
|
104
105
|
|
|
105
106
|
## drain
|
|
106
107
|
|
|
107
|
-
`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.
|
|
108
|
+
`status`, `add`, `withdraw` 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.
|
|
108
109
|
|
|
109
110
|
See [loop.md](references/loop.md) for an unclear result or `--worktree-root`. 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:
|
|
110
111
|
|
|
@@ -113,7 +114,7 @@ See [loop.md](references/loop.md) for an unclear result or `--worktree-root`. Th
|
|
|
113
114
|
| Claude | `claude -p "<prompt>" --dangerously-skip-permissions --add-dir "<git dir>"` (drain prepends `--settings <temporary-policy.json> --no-chrome` and any `--disallowedTools=<list>` denies after `claude`) |
|
|
114
115
|
| Codex | `bash "<DEVLYN_SHARED_DIR>/codex-monitored.sh" --skip-git-repo-check -s workspace-write -c 'sandbox_workspace_write.writable_roots=["<git dir>","{worktree_git_dir}"]' -c 'sandbox_workspace_write.network_access=true' "<prompt>"` |
|
|
115
116
|
|
|
116
|
-
|
|
117
|
+
`<prompt>` carries the absolute binding values, while `{packet}` stays literal for the drain to fill:
|
|
117
118
|
|
|
118
119
|
```text
|
|
119
120
|
Execute the devlyn loop task packet {packet}: read its workerPrompt when present; read methodology.defaults.path when named and apply those verified defaults, with explicit user and native owner/project instructions taking precedence, except a Devlyn managed block (devlyn:instructions markers), which those defaults replace; work only in its owned worktree under the installed instructions; meet its obligations, then write its submission and any review records as <DEVLYN_SKILL_DIR>/references/executor.md specifies. Use the literal DEVLYN_SKILL_DIR and DEVLYN_SHARED_DIR bindings in workerPrompt when present; otherwise set DEVLYN_SKILL_DIR=<DEVLYN_SKILL_DIR> and DEVLYN_SHARED_DIR=<DEVLYN_SHARED_DIR> from these literal values. Never use inherited environment values. Never push, open a pull request or merge: the drain delivers.
|
|
@@ -18,7 +18,7 @@ Both print one JSON object; exit 1 is `BLOCKED` with a `reason`.
|
|
|
18
18
|
|
|
19
19
|
The host supplies the executor and maps its configured engine to the argv; the loop has no model preference. Each `{packet}` in the argv is replaced by the task packet path, and each `{worktree_git_dir}` by the task worktree's absolute Git directory. The executor runs with the task worktree as its working directory and the null device as stdin; its stdout and stderr are appended to `executor.stdout` and `executor.stderr` in the packet's `evidence_dir`, never a pipe.
|
|
20
20
|
|
|
21
|
-
`<common Gitdir>/devlyn-loops/drain.lock` is held for the controller's lifetime, so a second drain from any worktree of the repository is refused. `queue.lock` is held only to read the add records and queue files, to add a loop and to make terminal transitions, so `add` works during a drain.
|
|
21
|
+
`<common Gitdir>/devlyn-loops/drain.lock` is held for the controller's lifetime, so a second drain from any worktree of the repository is refused. `queue.lock` is held only to read the add records and queue files, to add or withdraw a loop and to make terminal transitions, so `add` works during a drain.
|
|
22
22
|
|
|
23
23
|
## Steps
|
|
24
24
|
|
|
@@ -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** 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.
|
|
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. A merged delivery whose cleanup is refused settles (`COMPLETE`) with what the refusal guards 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 |
|
|
@@ -515,6 +515,8 @@ class AcceptanceTests(unittest.TestCase):
|
|
|
515
515
|
self.addCleanup(self.temp.cleanup)
|
|
516
516
|
self.root = Path(self.temp.name).resolve()
|
|
517
517
|
self.work = self.root / "work tree"
|
|
518
|
+
# A commit's detached automatic maintenance would still write into .git while a test reads the tree.
|
|
519
|
+
(self.root / "gitconfig").write_text("[maintenance]\n\tauto = false\n", encoding="utf-8")
|
|
518
520
|
env = {"GIT_CONFIG_NOSYSTEM": "1", "GIT_CONFIG_GLOBAL": str(self.root / "gitconfig"), "GIT_AUTHOR_NAME": "Fixture",
|
|
519
521
|
"GIT_AUTHOR_EMAIL": "fixture@example.invalid", "GIT_COMMITTER_NAME": "Fixture", "GIT_COMMITTER_EMAIL": "fixture@example.invalid"}
|
|
520
522
|
self.saved = {key: os.environ.get(key) for key in env}
|
|
@@ -543,6 +545,15 @@ class AcceptanceTests(unittest.TestCase):
|
|
|
543
545
|
self.g("commit", "-q", "-m", message)
|
|
544
546
|
return self.g("rev-parse", "HEAD")
|
|
545
547
|
|
|
548
|
+
def test_fixture_commit_starts_no_automatic_maintenance(self):
|
|
549
|
+
# Its detached child would still write into .git while a test reads the tree or cleanup removes it.
|
|
550
|
+
from unittest import mock
|
|
551
|
+
trace = self.root / "trace.json"
|
|
552
|
+
with mock.patch.dict(os.environ, GIT_TRACE2_EVENT=str(trace)):
|
|
553
|
+
self.g("commit", "-q", "--allow-empty", "-m", "traced")
|
|
554
|
+
children = [line for line in trace.read_text(encoding="utf-8").splitlines() if '"event":"child_start"' in line]
|
|
555
|
+
self.assertEqual(children, [])
|
|
556
|
+
|
|
546
557
|
def inputs(self, expected=None, review=()):
|
|
547
558
|
base = self.g("rev-parse", "HEAD")
|
|
548
559
|
files = {"docs/specs/l/meta.md": "# meta\n", "docs/specs/l/t/spec.md": "# task R1 R2\n",
|