devlyn-cli 4.1.0 → 4.2.0
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 +10 -94
- package/CLAUDE.md +7 -160
- package/LICENSE +21 -0
- package/README.md +65 -110
- package/bin/devlyn.js +95 -116
- package/bin/instruction-templates.json +43 -2
- package/bin/instructions.js +29 -8
- package/config/skills/_shared/adapters/claude.md +8 -85
- package/config/skills/_shared/adapters/codex.md +14 -34
- package/config/skills/_shared/adapters/omp.md +1 -11
- package/config/skills/_shared/codex-monitored.sh +4 -54
- package/config/skills/_shared/engine-doctor.sh +18 -58
- package/config/skills/_shared/engine-preflight.md +15 -47
- package/config/skills/_shared/expected-contract.py +242 -0
- package/config/skills/_shared/expected.schema.json +10 -95
- package/config/skills/_shared/invocation-receipt.py +26 -757
- package/config/skills/_shared/platform-support.py +37 -28
- package/config/skills/_shared/role-config.py +100 -421
- package/config/skills/_shared/runtime-principles.md +17 -93
- package/config/skills/_shared/task-complete.py +490 -285
- package/config/skills/_shared/task-completion.md +235 -0
- package/config/skills/devlyn-engines/SKILL.md +9 -11
- package/config/skills/devlyn-ideate/SKILL.md +57 -107
- package/config/skills/devlyn-ideate/references/elicitation.md +18 -99
- package/config/skills/devlyn-ideate/references/loop.md +110 -0
- package/config/skills/devlyn-ideate/references/package-format.md +128 -0
- package/config/skills/devlyn-ideate/scripts/acceptance.py +795 -0
- package/config/skills/devlyn-ideate/scripts/queue.py +1780 -0
- package/package.json +2 -3
- package/config/commit-conventions.md +0 -28
- package/config/skills/_shared/adapters/README.md +0 -74
- package/config/skills/_shared/adapters/grok.md +0 -100
- package/config/skills/_shared/archive_run.py +0 -969
- package/config/skills/_shared/codex-config.md +0 -81
- package/config/skills/_shared/collect-codex-findings.py +0 -505
- package/config/skills/_shared/finish-gate.py +0 -521
- package/config/skills/_shared/grok-anchor-guard.py +0 -78
- package/config/skills/_shared/judge-output-parser.py +0 -200
- package/config/skills/_shared/judge-role-evidence.py +0 -320
- package/config/skills/_shared/phase-prompt-render.py +0 -183
- package/config/skills/_shared/process-evidence.py +0 -926
- package/config/skills/_shared/resolve-bootstrap.py +0 -1410
- package/config/skills/_shared/resolve-stop-hook.py +0 -359
- package/config/skills/_shared/run-bounded.py +0 -58
- package/config/skills/_shared/spec-verify-check.py +0 -5083
- package/config/skills/_shared/state-phase-write.py +0 -5989
- package/config/skills/_shared/terminal-claim-check.py +0 -777
- package/config/skills/_shared/verify-merge-findings.py +0 -3528
- package/config/skills/devlyn-design-ui/SKILL.md +0 -650
- package/config/skills/devlyn-ideate/references/from-spec-mode.md +0 -61
- package/config/skills/devlyn-ideate/references/project-mode.md +0 -80
- package/config/skills/devlyn-ideate/references/spec-template.md +0 -121
- package/config/skills/devlyn-ideate/references/templates/decision.md +0 -48
- package/config/skills/devlyn-ideate/references/templates/roadmap.md +0 -50
- package/config/skills/devlyn-ideate/references/templates/vision.md +0 -44
- package/config/skills/devlyn-queue/SKILL.md +0 -66
- package/config/skills/devlyn-queue/scripts/append.py +0 -94
- package/config/skills/devlyn-resolve/SKILL.md +0 -379
- package/config/skills/devlyn-resolve/references/free-form-mode.md +0 -71
- package/config/skills/devlyn-resolve/references/outer-loop.md +0 -23
- package/config/skills/devlyn-resolve/references/phases/build-gate.md +0 -75
- package/config/skills/devlyn-resolve/references/phases/cleanup.md +0 -33
- package/config/skills/devlyn-resolve/references/phases/implement.md +0 -44
- package/config/skills/devlyn-resolve/references/phases/plan.md +0 -39
- package/config/skills/devlyn-resolve/references/phases/probe-derive.md +0 -261
- package/config/skills/devlyn-resolve/references/phases/surface-close.md +0 -34
- package/config/skills/devlyn-resolve/references/phases/verify.md +0 -310
- package/config/skills/devlyn-resolve/references/state-schema.md +0 -152
- package/config/skills/devlyn-resolve/references/task-completion.md +0 -160
- package/config/templates/prompt-templates.md +0 -71
- package/config/templates/template-feature.spec.md +0 -255
- package/config/templates/template-product-spec.md +0 -680
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Render exact phase-prompt bytes and print their SHA-256 digest."""
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import runpy
|
|
6
|
-
import argparse
|
|
7
|
-
import hashlib
|
|
8
|
-
import os
|
|
9
|
-
import pathlib
|
|
10
|
-
import re
|
|
11
|
-
import tempfile
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
EXCLUDED_ADAPTER_SECTION = re.compile(
|
|
15
|
-
rb"^## (?:Role eligibility|Invocation)(?:\r?\n|\Z).*?(?=^## |\Z)",
|
|
16
|
-
re.MULTILINE | re.DOTALL,
|
|
17
|
-
)
|
|
18
|
-
EXCLUDED_ADAPTER_HEADING = re.compile(
|
|
19
|
-
rb"^## (?:Role eligibility|Invocation)\r?$", re.MULTILINE
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def project_adapter(content: bytes) -> bytes:
|
|
24
|
-
projected = EXCLUDED_ADAPTER_SECTION.sub(b"", content)
|
|
25
|
-
assert EXCLUDED_ADAPTER_HEADING.search(projected) is None
|
|
26
|
-
return projected
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def validate_plan_context(task_context: pathlib.Path, content: bytes) -> None:
|
|
30
|
-
if task_context.name != "plan.task-context":
|
|
31
|
-
return
|
|
32
|
-
working_directory = pathlib.Path.cwd()
|
|
33
|
-
expected = (
|
|
34
|
-
b"Working directory: "
|
|
35
|
-
+ os.fsencode(working_directory)
|
|
36
|
-
+ b"\nPlan output: "
|
|
37
|
-
+ os.fsencode(working_directory / ".devlyn" / "plan.md")
|
|
38
|
-
+ b"\n"
|
|
39
|
-
)
|
|
40
|
-
if not content.startswith(expected):
|
|
41
|
-
raise SystemExit("error: invalid PLAN task-context header")
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def render_prompt(
|
|
45
|
-
adapter: pathlib.Path,
|
|
46
|
-
canonical_body: pathlib.Path,
|
|
47
|
-
task_context: pathlib.Path,
|
|
48
|
-
output: pathlib.Path,
|
|
49
|
-
) -> str:
|
|
50
|
-
try:
|
|
51
|
-
adapter_bytes = adapter.read_bytes()
|
|
52
|
-
body_bytes = canonical_body.read_bytes()
|
|
53
|
-
context_bytes = task_context.read_bytes()
|
|
54
|
-
except OSError as exc:
|
|
55
|
-
raise SystemExit(f"error: prompt input unreadable: {exc}") from exc
|
|
56
|
-
projected_adapter = project_adapter(adapter_bytes)
|
|
57
|
-
validate_plan_context(task_context, context_bytes)
|
|
58
|
-
rendered = projected_adapter + body_bytes + context_bytes
|
|
59
|
-
rendered = rendered.rstrip(b"\n")
|
|
60
|
-
if not output.parent.is_dir():
|
|
61
|
-
raise SystemExit(f"error: prompt output parent is not a directory: {output.parent}")
|
|
62
|
-
fd, temporary = tempfile.mkstemp(dir=output.parent, prefix=output.name + ".tmp.")
|
|
63
|
-
temporary_path = pathlib.Path(temporary)
|
|
64
|
-
try:
|
|
65
|
-
with os.fdopen(fd, "wb") as handle:
|
|
66
|
-
handle.write(rendered)
|
|
67
|
-
handle.flush()
|
|
68
|
-
os.fsync(handle.fileno())
|
|
69
|
-
temporary_path.replace(output)
|
|
70
|
-
except BaseException:
|
|
71
|
-
temporary_path.unlink(missing_ok=True)
|
|
72
|
-
raise
|
|
73
|
-
return hashlib.sha256(rendered).hexdigest()
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def self_test() -> int:
|
|
77
|
-
with tempfile.TemporaryDirectory(prefix="phase-prompt-render-") as raw:
|
|
78
|
-
root = pathlib.Path(raw)
|
|
79
|
-
adapter = root / "adapter.md"
|
|
80
|
-
body = root / "plan.md"
|
|
81
|
-
context = root / "plan.task-context"
|
|
82
|
-
output = root / ".devlyn" / "plan.prompt"
|
|
83
|
-
output.parent.mkdir()
|
|
84
|
-
original_directory = pathlib.Path.cwd()
|
|
85
|
-
os.chdir(root)
|
|
86
|
-
try:
|
|
87
|
-
working_directory = pathlib.Path.cwd()
|
|
88
|
-
adapter_bytes = (
|
|
89
|
-
b"# Claude adapter\r\n"
|
|
90
|
-
b"## Identity\r\nkept-identity-\xff\r\n"
|
|
91
|
-
b"## Role eligibility\nremove-role\r\n"
|
|
92
|
-
b"## Output discipline\r\nkept-output-\x80\n"
|
|
93
|
-
b"## Invocation\r\nremove-invocation\n"
|
|
94
|
-
b"## Anti-patterns\nkept-tail-without-newline"
|
|
95
|
-
)
|
|
96
|
-
projected_adapter = (
|
|
97
|
-
b"# Claude adapter\r\n"
|
|
98
|
-
b"## Identity\r\nkept-identity-\xff\r\n"
|
|
99
|
-
b"## Output discipline\r\nkept-output-\x80\n"
|
|
100
|
-
b"## Anti-patterns\nkept-tail-without-newline"
|
|
101
|
-
)
|
|
102
|
-
for metadata_free in (
|
|
103
|
-
b"# Codex adapter\r\n## Identity\nkept-\xfe",
|
|
104
|
-
b"# omp adapter\n## Output discipline\r\nkept\x81",
|
|
105
|
-
):
|
|
106
|
-
assert project_adapter(metadata_free) == metadata_free
|
|
107
|
-
|
|
108
|
-
body_bytes = b"# PHASE 1 \xe2\x80\x94 PLAN\n"
|
|
109
|
-
context_bytes = (
|
|
110
|
-
b"Working directory: "
|
|
111
|
-
+ os.fsencode(working_directory)
|
|
112
|
-
+ b"\nPlan output: "
|
|
113
|
-
+ os.fsencode(working_directory / ".devlyn" / "plan.md")
|
|
114
|
-
+ b"\ncontext-without-newline"
|
|
115
|
-
)
|
|
116
|
-
for path, content in (
|
|
117
|
-
(adapter, adapter_bytes),
|
|
118
|
-
(body, body_bytes),
|
|
119
|
-
(context, context_bytes),
|
|
120
|
-
):
|
|
121
|
-
path.write_bytes(content)
|
|
122
|
-
expected = projected_adapter + body_bytes + context_bytes
|
|
123
|
-
digest = render_prompt(adapter, body, context, output)
|
|
124
|
-
assert output.read_bytes() == expected
|
|
125
|
-
assert digest == hashlib.sha256(expected).hexdigest()
|
|
126
|
-
assert render_prompt(adapter, body, context, output) == digest
|
|
127
|
-
assert output.read_bytes() == expected
|
|
128
|
-
|
|
129
|
-
context_prefix = context_bytes.removesuffix(b"context-without-newline")
|
|
130
|
-
for terminal_lfs in (b"", b"\n", b"\n\n"):
|
|
131
|
-
case_context = context_prefix + b"context-terminal-lf-case" + terminal_lfs
|
|
132
|
-
context.write_bytes(case_context)
|
|
133
|
-
expected = (projected_adapter + body_bytes + case_context).rstrip(b"\n")
|
|
134
|
-
digest = render_prompt(adapter, body, context, output)
|
|
135
|
-
written = output.read_bytes()
|
|
136
|
-
assert not written.endswith(b"\n")
|
|
137
|
-
assert digest == hashlib.sha256(written).hexdigest()
|
|
138
|
-
assert written == expected
|
|
139
|
-
|
|
140
|
-
invalid_contexts = (
|
|
141
|
-
b"context-without-header",
|
|
142
|
-
b"Working directory: relative\nPlan output: relative/.devlyn/plan.md\n",
|
|
143
|
-
b"Working directory: /mismatch\nPlan output: /mismatch/.devlyn/plan.md\n",
|
|
144
|
-
)
|
|
145
|
-
for invalid_context in invalid_contexts:
|
|
146
|
-
context.write_bytes(invalid_context)
|
|
147
|
-
output.write_bytes(b"unchanged")
|
|
148
|
-
try:
|
|
149
|
-
render_prompt(adapter, body, context, output)
|
|
150
|
-
except SystemExit:
|
|
151
|
-
pass
|
|
152
|
-
else:
|
|
153
|
-
raise AssertionError("invalid PLAN task-context header accepted")
|
|
154
|
-
assert output.read_bytes() == b"unchanged"
|
|
155
|
-
finally:
|
|
156
|
-
os.chdir(original_directory)
|
|
157
|
-
print("SELFTEST PASS: projected exact bytes + PLAN context validation")
|
|
158
|
-
return 0
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
def main() -> int:
|
|
162
|
-
parser = argparse.ArgumentParser(description=__doc__)
|
|
163
|
-
parser.add_argument("--adapter", type=pathlib.Path)
|
|
164
|
-
parser.add_argument("--canonical-body", type=pathlib.Path)
|
|
165
|
-
parser.add_argument("--task-context", type=pathlib.Path)
|
|
166
|
-
parser.add_argument("--output", type=pathlib.Path)
|
|
167
|
-
parser.add_argument("--self-test", action="store_true")
|
|
168
|
-
args = parser.parse_args()
|
|
169
|
-
if args.self_test:
|
|
170
|
-
if any((args.adapter, args.canonical_body, args.task_context, args.output)):
|
|
171
|
-
parser.error("render paths are not allowed with --self-test")
|
|
172
|
-
return self_test()
|
|
173
|
-
if not all((args.adapter, args.canonical_body, args.task_context, args.output)):
|
|
174
|
-
parser.error(
|
|
175
|
-
"--adapter, --canonical-body, --task-context, and --output are required"
|
|
176
|
-
)
|
|
177
|
-
print(render_prompt(args.adapter, args.canonical_body, args.task_context, args.output))
|
|
178
|
-
return 0
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if __name__ == "__main__":
|
|
182
|
-
runpy.run_path(str(pathlib.Path(__file__).with_name("platform-support.py")))["configure_utf8"]()
|
|
183
|
-
raise SystemExit(main())
|