devlyn-cli 4.3.21 → 4.3.24
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 +1 -1
- package/CLAUDE.md +1 -1
- package/README.md +1 -1
- package/bin/devlyn.js +26 -1
- package/bin/harness-activation.js +11 -13
- package/bin/instruction-templates.json +26 -0
- package/bin/instructions.js +1 -1
- package/bin/skill-history.json +15 -1
- package/config/skills/_shared/bring-in.py +810 -0
- package/config/skills/_shared/harness-store.py +18 -6
- package/config/skills/_shared/runtime-principles.md +2 -2
- package/config/skills/_shared/storage-checkout.py +117 -0
- package/config/skills/_shared/storage-client.py +1180 -0
- package/config/skills/_shared/task-complete.py +900 -88
- package/config/skills/_shared/task-completion.md +78 -6
- package/config/skills/_shared/warm-build-dirs.py +53 -3
- package/config/skills/devlyn-ideate/SKILL.md +2 -2
- package/config/skills/devlyn-ideate/references/loop.md +4 -4
- package/config/skills/devlyn-ideate/scripts/queue.py +70 -19
- package/config/storage-client-contract.json +7 -0
- package/harness/AGENTS.md +2 -2
- package/harness/CLAUDE.md +2 -2
- package/native/claude/.claude-plugin/plugin.json +1 -1
- package/native/claude/skills/_shared/bring-in.py +810 -0
- package/native/claude/skills/_shared/harness-store.py +18 -6
- package/native/claude/skills/_shared/runtime-principles.md +2 -2
- package/native/claude/skills/_shared/storage-checkout.py +117 -0
- package/native/claude/skills/_shared/storage-client.py +1180 -0
- package/native/claude/skills/_shared/task-complete.py +900 -88
- package/native/claude/skills/_shared/task-completion.md +78 -6
- package/native/claude/skills/_shared/warm-build-dirs.py +53 -3
- package/native/claude/skills/devlyn-ideate/SKILL.md +2 -2
- package/native/claude/skills/devlyn-ideate/references/loop.md +4 -4
- package/native/claude/skills/devlyn-ideate/scripts/queue.py +70 -19
- 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 +810 -0
- package/native/codex/plugins/devlyn/skills/_shared/harness-store.py +18 -6
- package/native/codex/plugins/devlyn/skills/_shared/runtime-principles.md +2 -2
- package/native/codex/plugins/devlyn/skills/_shared/storage-checkout.py +117 -0
- package/native/codex/plugins/devlyn/skills/_shared/storage-client.py +1180 -0
- package/native/codex/plugins/devlyn/skills/_shared/task-complete.py +900 -88
- package/native/codex/plugins/devlyn/skills/_shared/task-completion.md +78 -6
- package/native/codex/plugins/devlyn/skills/_shared/warm-build-dirs.py +53 -3
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/SKILL.md +2 -2
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/loop.md +4 -4
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/scripts/queue.py +70 -19
- package/optional-skills/better-auth-setup/SKILL.md +9 -3
- package/optional-skills/better-auth-setup/references/proxy-setup.md +10 -3
- package/package.json +1 -1
- package/release.json +93 -43
|
@@ -169,16 +169,26 @@ def packet_schema(packet):
|
|
|
169
169
|
resource(packet.get("workerPrompt"))
|
|
170
170
|
|
|
171
171
|
|
|
172
|
-
def verify_packet(packet, path, skills):
|
|
172
|
+
def verify_packet(packet, path, skills, *, accepted_receipt=None):
|
|
173
173
|
packet_schema(packet)
|
|
174
174
|
if packet.get("schema_version", 1) == 1:
|
|
175
175
|
return # Preserve v1 execution without upgrading its resources.
|
|
176
176
|
receipt = packet.get("receipt")
|
|
177
177
|
require(isinstance(receipt, str) and "\0" not in receipt and Path(receipt).is_absolute()
|
|
178
178
|
and Path(receipt).name == "receipt.json" and Path(receipt).parent.parent.name == "devlyn-completion"
|
|
179
|
-
and Path(receipt) == Path(path).with_name("receipt.json"),
|
|
179
|
+
and (accepted_receipt is not None or Path(receipt) == Path(path).with_name("receipt.json")),
|
|
180
180
|
"malformed packet receipt path")
|
|
181
|
-
|
|
181
|
+
common = Path(receipt).parent.parent.parent
|
|
182
|
+
if accepted_receipt is not None:
|
|
183
|
+
saved = accepted_receipt
|
|
184
|
+
require(saved.get('acceptance') and saved.get('id') == Path(receipt).parent.name
|
|
185
|
+
and saved.get('common_gitdir') == str(common) and saved.get('worktree') == packet['worktree'],
|
|
186
|
+
'accepted packet receipt binding changed')
|
|
187
|
+
original = Path(receipt)
|
|
188
|
+
archived = common / 'devlyn-completion/retired' / (
|
|
189
|
+
saved['id'] + '.' + saved.get('storage', {}).get('request', {}).get('operation_id', '')) / 'receipt.json'
|
|
190
|
+
require(Path(path).with_name('receipt.json') in (original, archived), 'accepted packet custody path changed')
|
|
191
|
+
binding = read_binding(common)
|
|
182
192
|
if packet["harness"] is None:
|
|
183
193
|
require(binding is None, "bound task packet has no harness/defaults source")
|
|
184
194
|
# An unbound installation's own defaults change with its normal updates: never gate acceptance or delivery on
|
|
@@ -193,15 +203,17 @@ def verify_packet(packet, path, skills):
|
|
|
193
203
|
defaults = {"source": "release", **binding["defaults"]}
|
|
194
204
|
require(packet["methodology"]["defaults"] == defaults, "packet defaults differ from queue binding")
|
|
195
205
|
expected_runner = [str(Path(ref["root"]) / "config/skills/devlyn-ideate/scripts/acceptance.py"),
|
|
196
|
-
"run", "--packet", str(
|
|
206
|
+
"run", "--packet", str(Path(receipt).with_name('packet.json'))]
|
|
197
207
|
require(isinstance(packet.get("runner"), list) and packet["runner"][1:] == expected_runner, "packet runner differs from bound release")
|
|
198
208
|
prompt = packet["workerPrompt"]
|
|
199
|
-
|
|
209
|
+
actual_prompt = file_identity(Path(path).parent / 'worker-prompt.md')
|
|
210
|
+
require(prompt["path"] == str(Path(receipt).parent / "worker-prompt.md") and actual_prompt['sha256'] == prompt['sha256'],
|
|
200
211
|
"worker prompt changed or missing; restore the bound resource")
|
|
201
212
|
for entry in packet["methodology"]["projectInstructions"]:
|
|
202
213
|
name = Path(entry["path"]).name
|
|
203
214
|
require(name in ("CLAUDE.md", "AGENTS.md") and entry["path"] == str(Path(packet["worktree"]) / name),
|
|
204
215
|
"project instruction path escapes packet worktree")
|
|
205
|
-
|
|
216
|
+
repository = ['--git-dir', str(common)] if accepted_receipt is not None else ['-C', packet['worktree']]
|
|
217
|
+
committed = subprocess.run(["git", *repository, "cat-file", "blob", f"{packet['inputs_sha']}:{name}"], capture_output=True)
|
|
206
218
|
require(committed.returncode == 0 and hashlib.sha256(committed.stdout).hexdigest() == entry["sha256"],
|
|
207
219
|
f"project instruction differs from inputs commit: {name}")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
The following Devlyn instructions are defaults for this session.
|
|
1
|
+
The following Devlyn instructions are defaults for this session. Explicit user and native owner/project instructions take precedence, except a Devlyn managed block (devlyn:instructions markers), which these defaults replace. They change no other engine instructions or permissions.
|
|
2
2
|
|
|
3
3
|
# Project Instructions
|
|
4
4
|
|
|
@@ -15,7 +15,7 @@ Seven rules govern every change. Cite them by name when a decision touches one.
|
|
|
15
15
|
1. **No workaround** — fix the root cause, never the symptom. No `any`, no `@ts-ignore`, no silent `catch`, no hardcoded fallback that hides a broken contract. No config bypasses.
|
|
16
16
|
2. **No overengineering** — smallest sufficient change, one responsibility per module. New abstractions need an observed failure. Subtractive-first: ask "what can I delete instead?" before writing anything new. Remove code your change makes unused; only report unrelated pre-existing dead code.
|
|
17
17
|
3. **No guesswork** — verify with the actual files, logs, diffs, and run output before forming conclusions. State the falsifiable prediction BEFORE the experiment; record raw results AFTER. Retroactive prediction edits are dishonest.
|
|
18
|
-
4. **Worldclass** —
|
|
18
|
+
4. **Worldclass** — zero CRITICAL, zero HIGH security/design findings on the shippable path. Any review is one pass in which every reviewer, including each one the user requires, reads the source the checks ran on; fix each finding or reject it with a reason, re-check the fixes and the invariants they touch, and repeat the full review only while a CRITICAL or HIGH finding remains.
|
|
19
19
|
5. **Best practice** — idiomatic for the language and framework. Use standard primitives; do not hand-roll what the library already provides.
|
|
20
20
|
6. **Optimized** — use efficient code and avoid unnecessary work without sacrificing correctness or the user's requirements.
|
|
21
21
|
7. **Production ready** — error states are explicit and visible; behavior under failure is what the user expects, not silent corruption.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Protocol-1 finite checkout adapter. Rust owns the allocation subreceipt."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path, PurePosixPath, PureWindowsPath
|
|
8
|
+
import runpy
|
|
9
|
+
import subprocess
|
|
10
|
+
|
|
11
|
+
storage = runpy.run_path(str(Path(__file__).with_name('storage-client.py')))
|
|
12
|
+
require = storage['require']
|
|
13
|
+
validate = storage['validate']
|
|
14
|
+
StorageRefusal = storage['StorageRefusal']
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def validate_request(op, request, *, windows=None):
|
|
18
|
+
destination = {'state': ('absent', 'precreated_empty'), 'path': 'str'}
|
|
19
|
+
if type(request.get('destination')) is dict and request['destination'].get('state') == 'precreated_empty':
|
|
20
|
+
destination['identity'] = 'identity'
|
|
21
|
+
shapes = {
|
|
22
|
+
'capabilities': {},
|
|
23
|
+
'allocate': {'adapter_protocol': 'u32', 'operation_id': 'str', 'control_directory': 'str',
|
|
24
|
+
'repository': 'str', 'baseline': 'str', 'branch': 'str', 'owner': 'owner',
|
|
25
|
+
'external_receipt': 'str', 'destination': destination, 'cache_policy': ('cargo_worktree_v1',)},
|
|
26
|
+
'reconcile-for-removal': {'adapter_protocol': 'u32', 'allocation_operation_id': 'str',
|
|
27
|
+
'external_receipt': 'str', 'removal_operation_id': 'str', 'worktree_identity?': 'identity?'},
|
|
28
|
+
'unenroll': {'adapter_protocol': 'u32', 'operation_id': 'str'},
|
|
29
|
+
}
|
|
30
|
+
require(op in shapes, 'unknown checkout adapter operation', 'storage_invalid')
|
|
31
|
+
validate(request, shapes[op], exact=True, windows=windows)
|
|
32
|
+
if op == 'capabilities':
|
|
33
|
+
return request
|
|
34
|
+
require(request['adapter_protocol'] == 1, 'unsupported checkout adapter protocol', 'storage_invalid')
|
|
35
|
+
for field in ('operation_id', 'allocation_operation_id', 'removal_operation_id'):
|
|
36
|
+
if field in request:
|
|
37
|
+
storage['canonical_operation'](request[field])
|
|
38
|
+
path_type = PureWindowsPath if (os.name == 'nt' if windows is None else windows) else PurePosixPath
|
|
39
|
+
for name in ('repository', 'control_directory', 'external_receipt'):
|
|
40
|
+
if name in request:
|
|
41
|
+
path = path_type(request[name])
|
|
42
|
+
require(path.is_absolute() and '..' not in path.parts and '.' not in request[name].replace('\\', '/').split('/'),
|
|
43
|
+
'adapter paths must be absolute without traversal', 'storage_invalid')
|
|
44
|
+
if op == 'allocate':
|
|
45
|
+
import re
|
|
46
|
+
require(re.fullmatch(r'(?:[0-9a-f]{40}|[0-9a-f]{64})', request['baseline']),
|
|
47
|
+
'baseline must be an exact commit', 'storage_invalid')
|
|
48
|
+
return request
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def validate_closure(value):
|
|
52
|
+
require(type(value) is dict, 'positive reservation closure missing')
|
|
53
|
+
tag = value.get('established_by')
|
|
54
|
+
shapes = {'released': {'established_by': ('released',), 'reservation': 'reservation'},
|
|
55
|
+
'authority_expired': {'established_by': ('authority_expired',), 'operation_id': 'str'},
|
|
56
|
+
'never_admitted': {'established_by': ('never_admitted',), 'operation_id': 'str'},
|
|
57
|
+
'legacy_unreserved': {'established_by': ('legacy_unreserved',), 'receipt_version': 'u32'}}
|
|
58
|
+
require(tag in shapes, 'positive reservation closure missing')
|
|
59
|
+
validate(value, shapes[tag])
|
|
60
|
+
return value
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def decode_reply(raw, op, code):
|
|
64
|
+
require(raw.endswith(b'\n') and b'\n' not in raw[:-1] and len(raw) <= storage['MAX_REPLY'],
|
|
65
|
+
'uncertain adapter output; replay the saved request')
|
|
66
|
+
value = storage['loads'](raw)
|
|
67
|
+
validate(value, {'adapter_protocol': 'u32', 'op': (op,)})
|
|
68
|
+
require(value['adapter_protocol'] == 1 and ('result' in value) != ('error' in value), 'invalid adapter envelope')
|
|
69
|
+
if 'error' in value:
|
|
70
|
+
error = value['error']
|
|
71
|
+
validate(error, {'code': 'str', 'phase': 'str', 'retryable': 'bool', 'receipt': 'str?', 'diagnostic': 'str'})
|
|
72
|
+
require(code == 2, 'adapter refusal exit mismatch')
|
|
73
|
+
refusal = StorageRefusal('storage_invalid' if error['code'] == 'invalid_request' else error['code'],
|
|
74
|
+
error['diagnostic'], operation=op, phase=error['phase'])
|
|
75
|
+
refusal.retryable = error['retryable']
|
|
76
|
+
raise refusal
|
|
77
|
+
require(code == 0, 'uncertain adapter exit; replay the saved request')
|
|
78
|
+
result = value['result']
|
|
79
|
+
if op == 'capabilities':
|
|
80
|
+
validate(result, {'cli_checkout_v1': 'bool', 'adapter_protocol': 'u32', 'receipt_version': 'u32',
|
|
81
|
+
'destination_modes': ['str'], 'helper_release': {'version': 'str', 'os': 'str', 'arch': 'str', 'sha256': 'str'}})
|
|
82
|
+
require(result['cli_checkout_v1'] and result['adapter_protocol'] == 1 and result['receipt_version'] == 3,
|
|
83
|
+
'incompatible checkout helper', 'storage_unsupported')
|
|
84
|
+
elif op == 'allocate':
|
|
85
|
+
validate(result, {'allocation_binding': {'baseline': 'str', 'branch': 'str', 'destination': 'str',
|
|
86
|
+
'destination_mode': {'state': ('absent', 'precreated_empty'), 'identity?': 'identity'},
|
|
87
|
+
'owner': 'owner', 'publication_owner': 'owner', 'receipt': 'str',
|
|
88
|
+
'adapter': {'operation_id': 'str', 'repository': 'str', 'control_directory': 'str', 'external_receipt': 'str'}},
|
|
89
|
+
'receipt': 'str', 'publications': ['published'], 'warnings': ['warning']})
|
|
90
|
+
validate_closure(result.get('closure_result'))
|
|
91
|
+
elif op == 'reconcile-for-removal':
|
|
92
|
+
validate(result, {'operation_id': 'str', 'cache_ids': ['str']})
|
|
93
|
+
require(len(result['cache_ids']) == len(set(result['cache_ids'])), 'duplicate removal cache ID')
|
|
94
|
+
validate_closure(result.get('closure_result'))
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def call(registration, op, request):
|
|
99
|
+
"""One attempt; uncertain stdout leaves the caller's durable request unchanged."""
|
|
100
|
+
validate_request(op, request)
|
|
101
|
+
raw = json.dumps(request, ensure_ascii=False, separators=(',', ':'), allow_nan=False).encode()+b'\n'
|
|
102
|
+
require(len(raw)-1 <= storage['MAX_REQUEST'], 'adapter request exceeds limit', 'storage_invalid')
|
|
103
|
+
try:
|
|
104
|
+
require(storage['regular_digest'](registration['helper_path']) == registration['helper_release']['sha256'],
|
|
105
|
+
'enrolled helper changed')
|
|
106
|
+
# No timeout: killing an allocating holder can turn replay into crash recovery.
|
|
107
|
+
# Spool output to disk so an invalid helper cannot exhaust Python memory.
|
|
108
|
+
import tempfile
|
|
109
|
+
with tempfile.TemporaryFile() as output, tempfile.TemporaryFile() as diagnostic:
|
|
110
|
+
process = subprocess.run([registration['helper_path'], 'storage-checkout', op, '--json'],
|
|
111
|
+
input=raw, stdout=output, stderr=diagnostic)
|
|
112
|
+
output.seek(0)
|
|
113
|
+
reply = output.read(storage['MAX_REPLY']+1)
|
|
114
|
+
return decode_reply(reply, op, process.returncode)
|
|
115
|
+
except OSError as exc:
|
|
116
|
+
raise StorageRefusal('storage_unavailable', f'helper outcome uncertain: {exc}; replay saved request',
|
|
117
|
+
operation=op, phase='helper') from exc
|