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
package/bin/instructions.js
CHANGED
|
@@ -119,6 +119,18 @@ function instructionParagraphs(text) {
|
|
|
119
119
|
return paragraphs;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// Claude Code expands an `@AGENTS.md` import line, except inside a fenced code block.
|
|
123
|
+
function importsAgentsMd(text) {
|
|
124
|
+
let fence = null;
|
|
125
|
+
for (const line of text.replace(/^\uFEFF/, '').split(/\r?\n/)) {
|
|
126
|
+
const marker = /^ {0,3}(`{3,}|~{3,})/.exec(line)?.[1];
|
|
127
|
+
if (marker && !fence) fence = marker;
|
|
128
|
+
else if (marker && marker[0] === fence[0] && marker.length >= fence.length && /^ {0,3}(?:`+|~+)\s*$/.test(line)) fence = null;
|
|
129
|
+
else if (!fence && line.trimEnd() === '@AGENTS.md') return true;
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
122
134
|
function customInstructions(text, name, template, managed = false) {
|
|
123
135
|
const known = new Set([
|
|
124
136
|
...legacyTemplates.paragraphs[name],
|
|
@@ -168,7 +180,9 @@ function retainFile(file, bytes) {
|
|
|
168
180
|
}
|
|
169
181
|
}
|
|
170
182
|
|
|
171
|
-
|
|
183
|
+
// `withBlock` false removes the devlyn defaults the file holds, keeping everything else. `write` false writes nothing
|
|
184
|
+
// and returns whether the file needs an update, a merge included.
|
|
185
|
+
function updateInstructions(name, initial = '', withBlock = true, write = true) {
|
|
172
186
|
const templatePath = path.join(__dirname, '..', name);
|
|
173
187
|
const template = fs.readFileSync(templatePath, 'utf8');
|
|
174
188
|
if (template.includes(BEGIN) || template.includes(END)) {
|
|
@@ -187,16 +201,18 @@ function updateInstructions(name) {
|
|
|
187
201
|
throw new InstructionError(`Instruction file must be a regular file; preserved: ${dest}. Move it aside and rerun, then merge your shared rules outside the managed block.`);
|
|
188
202
|
}
|
|
189
203
|
const before = exists ? fs.readFileSync(dest) : Buffer.alloc(0);
|
|
190
|
-
let current;
|
|
204
|
+
let current = initial;
|
|
191
205
|
try {
|
|
192
|
-
current = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(before);
|
|
206
|
+
if (exists) current = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(before);
|
|
193
207
|
} catch (cause) {
|
|
194
208
|
throw new InstructionError(`${name} must be UTF-8; original preserved. Convert its encoding and rerun installation.`, { cause });
|
|
195
209
|
}
|
|
196
210
|
const eol = current.match(/\r?\n/)?.[0] || '\n';
|
|
197
211
|
const block = managedBlock(template, eol);
|
|
212
|
+
const inserted = withBlock ? block : '';
|
|
198
213
|
const recovery = path.join(process.cwd(), '.devlyn', 'instructions');
|
|
199
214
|
const conflict = (reason) => {
|
|
215
|
+
if (!write) return true;
|
|
200
216
|
const incoming = path.join(recovery, `${name}.${digest(block)}.incoming`);
|
|
201
217
|
const backup = path.join(recovery, `${name}.${digest(before)}.backup`);
|
|
202
218
|
const guide = path.join(recovery, `${name}.${digest(before)}.${digest(block)}.merge.md`);
|
|
@@ -231,8 +247,11 @@ function updateInstructions(name) {
|
|
|
231
247
|
const custom = digest(normalize(previous)) === start[1] ? ''
|
|
232
248
|
: customInstructions(previous, name,
|
|
233
249
|
'Project-specific instructions outside this managed block take precedence over these defaults.\n\n' + template, true);
|
|
250
|
+
const after = current.slice(end.index + end[0].length);
|
|
234
251
|
content = current.slice(0, start.index + (start[0].startsWith('\uFEFF') ? 1 : 0))
|
|
235
|
-
+ custom + (custom ? eol : '') +
|
|
252
|
+
+ custom + (custom ? eol : '') + inserted + after;
|
|
253
|
+
// A block removed from the end takes along the blank lines that set it apart.
|
|
254
|
+
if (!inserted && !after) content = content.replace(/(\r?\n)(?:\r?\n)+$/, '$1');
|
|
236
255
|
} else {
|
|
237
256
|
const normalized = normalize(template);
|
|
238
257
|
const bodyStart = normalized.indexOf('## North Star\n');
|
|
@@ -246,14 +265,15 @@ function updateInstructions(name) {
|
|
|
246
265
|
if (ranges.length === 1 && !LEGACY.test(current.slice(0, ranges[0].start)
|
|
247
266
|
+ ranges[0].custom + current.slice(ranges[0].end))) {
|
|
248
267
|
const { start, end, custom } = ranges[0];
|
|
249
|
-
content = current.slice(0, start) + custom +
|
|
268
|
+
content = current.slice(0, start) + custom + inserted + current.slice(end);
|
|
250
269
|
} else {
|
|
251
270
|
const custom = customInstructions(current, name, template);
|
|
252
|
-
content = custom + (custom ? eol + eol : '') +
|
|
271
|
+
content = custom + (custom && inserted ? eol + eol : '') + inserted;
|
|
253
272
|
}
|
|
254
273
|
}
|
|
255
274
|
const bytes = Buffer.from(content);
|
|
256
275
|
if (bytes.equals(before)) return false;
|
|
276
|
+
if (!write) return true;
|
|
257
277
|
if (exists) {
|
|
258
278
|
const backup = path.join(recovery, `${name}.${digest(before)}.backup`);
|
|
259
279
|
retainFile(backup, before);
|
|
@@ -270,8 +290,9 @@ function updateInstructions(name) {
|
|
|
270
290
|
} finally {
|
|
271
291
|
if (fs.existsSync(temp)) fs.unlinkSync(temp);
|
|
272
292
|
}
|
|
273
|
-
console.log(` →
|
|
293
|
+
console.log(!exists ? ` → Created ${name}`
|
|
294
|
+
: ` → ${withBlock ? 'Updated Devlyn defaults in' : 'Removed Devlyn defaults from'} ${name}; project-specific instructions preserved`);
|
|
274
295
|
return true;
|
|
275
296
|
}
|
|
276
297
|
|
|
277
|
-
module.exports = { updateInstructions, InstructionError, instructionParagraphs, holdsDevlynDefaults };
|
|
298
|
+
module.exports = { updateInstructions, InstructionError, instructionParagraphs, holdsDevlynDefaults, importsAgentsMd };
|
|
@@ -1,92 +1,15 @@
|
|
|
1
1
|
# Claude adapter
|
|
2
2
|
|
|
3
|
-
> Source: <https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices>
|
|
4
|
-
|
|
5
3
|
## Invocation
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
`
|
|
10
|
-
|
|
5
|
+
A Claude executor starts as
|
|
6
|
+
`claude -p "<prompt>" --dangerously-skip-permissions --add-dir "<common git dir>"`,
|
|
7
|
+
where `<common git dir>` is `git rev-parse --path-format=absolute --git-common-dir`:
|
|
8
|
+
a linked task worktree commits there.
|
|
11
9
|
|
|
12
10
|
**Availability probe**: `command -v claude >/dev/null 2>&1`; record
|
|
13
11
|
`claude --version` as evidence. The probe is necessary, not sufficient:
|
|
14
|
-
`claude -p` needs network access to the Anthropic API, so a network-denying
|
|
15
|
-
sandbox (e.g. Codex CLI's default `workspace-write`) fails
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
`BLOCKED:claude-unavailable`; automatic escalation → solo + reported skip.
|
|
19
|
-
|
|
20
|
-
**Read-only pair-JUDGE call** (bounded by the VERIFY pair contract — at most
|
|
21
|
-
two targeted probes; the invoking phase sets `--effort`, pair-JUDGE uses
|
|
22
|
-
`medium`):
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
python3 "$DEVLYN_SHARED_DIR/run-bounded.py" 600 --stdin-file "<judge-prompt-file>" --record-transport -- claude -p \
|
|
26
|
-
--permission-mode dontAsk \
|
|
27
|
-
--tools "Read,Grep,Glob" --allowedTools "Read,Grep,Glob" \
|
|
28
|
-
--setting-sources project --strict-mcp-config --mcp-config '{"mcpServers":{}}' \
|
|
29
|
-
--effort medium \
|
|
30
|
-
> .devlyn/claude-judge.stdout 2> .devlyn/claude-judge.stderr
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
- Write the complete prompt as exact UTF-8 file bytes; no positional prompt or shell substitution. With `--record-transport`, the runner snapshots stdin and exclusively creates `<judge-prompt-file>.transport.json` to bind actual native argv and delivered bytes; an existing carrier rejects dispatch. Preserve it for explicit judge-role authentication. Ordinary `--stdin-file` reads require no output or writable input directory; without it, stdin remains DEVNULL.
|
|
34
|
-
- Omit `--model` — the CLI's configured default is used (zero-touch, same
|
|
35
|
-
rule as codex-config.md's "omit `-m`").
|
|
36
|
-
- `dontAsk` denies anything not allowlisted (official guide: "denies
|
|
37
|
-
anything not in your permissions.allow rules or the read-only command
|
|
38
|
-
set — useful for locked-down CI runs"); in `-p` mode a denied tool call
|
|
39
|
-
fails without prompting, so the child can never hang on a permission ask.
|
|
40
|
-
- The `--setting-sources project --strict-mcp-config --mcp-config
|
|
41
|
-
'{"mcpServers":{}}'` trio is the hermetic-child pattern: no user-global
|
|
42
|
-
settings, hooks, or MCP servers. `--bare` is NOT used — it skips
|
|
43
|
-
OAuth/keychain reads and requires `ANTHROPIC_API_KEY`, which
|
|
44
|
-
subscription-auth machines do not have.
|
|
45
|
-
- Findings come back as JSONL on stdout (same emission shape as the Codex
|
|
46
|
-
direction); the orchestrator writes the canonical
|
|
47
|
-
`.devlyn/verify.pair.findings.jsonl`. Raw stdout stays diagnostic at
|
|
48
|
-
`.devlyn/claude-judge.stdout`; `verify-merge-findings.py` blocks the run
|
|
49
|
-
if stdout contains findings the canonical file lacks.
|
|
50
|
-
- Exit 124 is a wall-budget abort (terminates the native child process tree; POSIX process group) → the orchestrator
|
|
51
|
-
writes `.devlyn/verify.pair.timeout.json`; budget abort ≠ availability failure
|
|
52
|
-
and the fail-closed availability rules above are unchanged.
|
|
53
|
-
- Do not pipe stdout (`| tail`, `| grep`); capture to file. Non-zero exit other
|
|
54
|
-
than 124 or empty stdout → spawn failure, fail closed per the probe rule above.
|
|
55
|
-
|
|
56
|
-
## Identity
|
|
57
|
-
|
|
58
|
-
You are Claude by Anthropic. Anthropic's prompt-engineering guide for this model governs your behavior on top of the canonical phase prompt below.
|
|
59
|
-
|
|
60
|
-
## Output discipline
|
|
61
|
-
|
|
62
|
-
You calibrate response length to task complexity automatically — keep simple lookups short, scale up only when the task warrants it. Do NOT pad with context the user didn't ask for. When the canonical body sets a structural format (XML, JSON, sections), follow it literally; do not silently restructure.
|
|
63
|
-
|
|
64
|
-
## Examples and structure
|
|
65
|
-
|
|
66
|
-
When prompt maintenance adds examples for Claude, prefer concise positive examples over lists of negative prohibitions. Wrap examples in `<example>` tags (or `<examples>` for several) so examples stay distinct from instructions and variable inputs.
|
|
67
|
-
|
|
68
|
-
## Tool-use posture
|
|
69
|
-
|
|
70
|
-
When the canonical body lists tools, use them when their result would change your answer. Make independent tool calls in parallel; chain only when one depends on another's output. Do not narrate "I'll now call X" preambles unless the canonical body requests progress updates.
|
|
71
|
-
|
|
72
|
-
## Effort and autonomy
|
|
73
|
-
|
|
74
|
-
For long-horizon coding, review, and agentic runs, assume the harness selected `high` or `xhigh` effort unless told otherwise. Spend that depth on upfront task/constraint understanding and end-state verification, not on verbose narration. If the user or orchestrator gives a complete task in one turn, proceed autonomously instead of requiring progressive clarification.
|
|
75
|
-
|
|
76
|
-
## Anti-patterns
|
|
77
|
-
|
|
78
|
-
You interpret instructions literally and explicitly. The official guide is explicit about three failure modes:
|
|
79
|
-
|
|
80
|
-
1. **Review-prompt self-filtering**: when the canonical body asks for findings, report every issue you find — including low-severity and low-confidence ones; do not filter for importance or confidence. The harness has a separate filter step.
|
|
81
|
-
2. **Subagent spawning**: do NOT spawn a subagent for work you can complete in a single response. Spawn only when the canonical body explicitly requests it OR when fanning out across independent items.
|
|
82
|
-
3. **Overengineering**: do NOT add files, abstractions, error handling, validation, or "future flexibility" beyond what the spec asks. A bug fix doesn't need surrounding cleanup. The right complexity is the minimum needed for the current task.
|
|
83
|
-
|
|
84
|
-
You do NOT need stronger imperatives ("CRITICAL!", "YOU MUST!") to follow rules. Normal phrasing is sufficient.
|
|
85
|
-
|
|
86
|
-
## Explicit role capability
|
|
87
|
-
|
|
88
|
-
`role-config.py` validates explicit CLI judge effort against this bounded native capability declaration. Source: Claude Code2.1.263 built-in Fable5.1 metadata (native SHA ef5d2909c8af49f31ab6d5487e90316777bc2fac170adfe8160716caa8aaf4f9, byte157907500; effort/max/xhigh gates159068038/159068443/159068839). This is option support, not model fitness. Unknown version/model effort support fails with actionable unsupported-role-option. Model-only requests pass the exact ID to the native CLI without this effort table and still require matching native result evidence; engine-only defaults remain available. Native Agent workers have no validated exact-model/effort transport here, so explicit worker fields are rejected instead of switching channels.
|
|
89
|
-
|
|
90
|
-
<!-- devlyn-effort 2.1.263 claude-fable-5-1 low,medium,high,xhigh,max -->
|
|
91
|
-
|
|
92
|
-
For an explicit CLI judge profile, use the resolved `--model`/`--effort` in the bounded read-only invocation and add `--output-format json`. Follow the canonical resolve `JUDGE_STEM` round-scoped argv/prompt/raw capture and `judge-role-evidence.py` derivation contract. Only its exact derived result goes to the findings collector; never feed the JSON wrapper directly. Omitted options keep that role’s existing defaults.
|
|
12
|
+
`claude -p` needs network access to the Anthropic API, so in a network-denying
|
|
13
|
+
sandbox (e.g. Codex CLI's default `workspace-write`) it fails even though the
|
|
14
|
+
binary resolves. A drain blocks when the executor cannot start, and a task
|
|
15
|
+
whose executor exits without a submission waits for the next drain.
|
|
@@ -1,34 +1,14 @@
|
|
|
1
|
-
# Codex
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Resolve the request in the fewest useful tool loops without sacrificing correctness. For retrieval tasks: start with one broad search using short discriminative keywords; make another retrieval call only when the top results don't answer the core question or a required fact / parameter / source is missing. For tool-heavy tasks, start with a brief preamble: a one-line acknowledgment of the request and the first step you'll take.
|
|
16
|
-
|
|
17
|
-
For a constrained-read VERIFY packet, judge the supplied material without tools; missing or unbound inputs require a verdict-binding BLOCKED finding. Keep the read-only sandbox and bounded review. Otherwise, for VERIFY JUDGE retrieval, batch related reads. After sealed parity proves a
|
|
18
|
-
canonical file and its `.agents` mirror are byte-identical, inspect the
|
|
19
|
-
canonical file only. Open self-test bodies or MECHANICAL raw streams only for a
|
|
20
|
-
named clause that remains unresolved after the broad pass.
|
|
21
|
-
|
|
22
|
-
## Anti-patterns
|
|
23
|
-
|
|
24
|
-
The official guide warns explicitly about carrying over instructions from older prompt stacks — earlier models needed more help, and process-heavy directives now narrow GPT-5.5's search space.
|
|
25
|
-
|
|
26
|
-
1. **Avoid absolute imperatives for judgment calls.** ALWAYS / NEVER / must / only are reserved for true safety invariants and required output fields. For judgment calls, use decision rules with conditions ("when X, do Y"). The canonical body uses this style; do not promote softer guidance to absolute rules.
|
|
27
|
-
2. **Don't over-specify process when the destination is clear.** If the canonical body names the outcome, choose the path; do not narrate every step.
|
|
28
|
-
3. **Stop rules are explicit.** When the canonical body or the harness asks you to stop / abstain / ask, follow the stop rule rather than retrying loops indefinitely. Loop-minimization does not outrank correctness or required citation.
|
|
29
|
-
|
|
30
|
-
## Prompt-maintenance cue
|
|
31
|
-
|
|
32
|
-
When asked to improve a failed prompt, act as a metaprompter for itself: name the observed failure, then propose the smallest instruction to add, remove, or relocate. Prefer subtractive changes before adding new rules; keep the canonical body model-neutral and put only GPT-specific tactics in this adapter.
|
|
33
|
-
|
|
34
|
-
Do not narrate internal deliberation. State results and decisions directly.
|
|
1
|
+
# Codex adapter
|
|
2
|
+
|
|
3
|
+
## Invocation
|
|
4
|
+
|
|
5
|
+
A Codex executor runs through `_shared/codex-monitored.sh`, never a raw or piped
|
|
6
|
+
`codex exec`: `bash "$DEVLYN_SHARED_DIR/codex-monitored.sh" <arguments for codex exec> "<prompt>"`.
|
|
7
|
+
The `workspace-write` sandbox keeps `.git` read-only, so an executor that commits adds as
|
|
8
|
+
writable roots the common Git directory (`git rev-parse --path-format=absolute --git-common-dir`)
|
|
9
|
+
plus, in a linked worktree, its own Git directory (`git rev-parse --path-format=absolute --git-dir`),
|
|
10
|
+
listed exactly because the sandbox protects it even under a listed parent. It also
|
|
11
|
+
denies network, which installing a new dependency needs:
|
|
12
|
+
`-s workspace-write -c 'sandbox_workspace_write.writable_roots=["<git dir>","<worktree git dir>"]' -c 'sandbox_workspace_write.network_access=true'`.
|
|
13
|
+
Deliver a multiline prompt as exact file bytes: set `DEVLYN_CODEX_PROMPT_FILE=<file>`
|
|
14
|
+
and pass the sole prompt argument `-`.
|
|
@@ -1,13 +1,3 @@
|
|
|
1
1
|
# oh-my-pi adapter
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
You are the model running behind the `omp` CLI. Follow the canonical phase prompt exactly; this adapter exists so oh-my-pi can be selected as a first-class native task worker by `/devlyn-resolve`.
|
|
6
|
-
|
|
7
|
-
## Output Discipline
|
|
8
|
-
|
|
9
|
-
Keep phase output machine-consumable and concise. Write the artifacts the canonical body names, update `.devlyn/pipeline.state.json` only where that phase owns the state write, and avoid narrative outside the required phase result.
|
|
10
|
-
|
|
11
|
-
## Fresh Context
|
|
12
|
-
|
|
13
|
-
Each native `task` tool invocation with a fresh `context` is a fresh phase worker. Do not call `/devlyn-resolve` recursively from inside a phase prompt; execute only the phase body you received.
|
|
3
|
+
Engine `omp`, the oh-my-pi CLI.
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
# (Round 2 finding #1 fix: shim alone does not defeat `| tail`; the
|
|
23
23
|
# wrapper must reject the pipe shape directly.)
|
|
24
24
|
# 2. Closes stdin unless DEVLYN_CODEX_PROMPT_FILE supplies the sole prompt.
|
|
25
|
-
# 3. Streams codex stdout to OUR stdout line-by-line — the
|
|
26
|
-
# stdout as the
|
|
27
|
-
#
|
|
25
|
+
# 3. Streams codex stdout to OUR stdout line-by-line — the caller reads
|
|
26
|
+
# stdout as the Codex reply, so we MUST NOT swallow it (e.g.
|
|
27
|
+
# `tail -n 200`). codex stderr forwards to OUR stderr.
|
|
28
28
|
# 4. Emits a `[codex-monitored] heartbeat` line every CODEX_MONITORED_HEARTBEAT
|
|
29
29
|
# seconds (default 30s) on STDERR while codex is alive. Heartbeat-on-stderr
|
|
30
30
|
# keeps the orchestrator's combined-output stream non-silent without
|
|
@@ -47,18 +47,10 @@
|
|
|
47
47
|
# CODEX_REAL_BIN when set, else `codex`.
|
|
48
48
|
# Set this when the shim has put us first
|
|
49
49
|
# on PATH.
|
|
50
|
-
# CODEX_MONITORED_ISOLATED — set non-empty for bounded read-only
|
|
51
|
-
# probe/judge calls that must ignore
|
|
52
|
-
# user config, project rules, session
|
|
53
|
-
# persistence, and hook side effects.
|
|
54
50
|
# CODEX_MONITORED_ALLOW_PIPED — set non-empty to skip the pipe-stdout
|
|
55
51
|
# refusal. Reserved for tests; don't use
|
|
56
52
|
# in skill prompts.
|
|
57
53
|
# DEVLYN_CODEX_PROMPT_FILE — exact binary stdin prompt; requires sole `-`.
|
|
58
|
-
# DEVLYN_INVOCATION_* — run/phase/round/workdir/prompt/session/
|
|
59
|
-
# receipt identity. When any is set, all
|
|
60
|
-
# are required and the wrapper seals a
|
|
61
|
-
# canonical invocation receipt.
|
|
62
54
|
|
|
63
55
|
set -uo pipefail
|
|
64
56
|
|
|
@@ -120,45 +112,6 @@ require_positive_int() {
|
|
|
120
112
|
require_positive_int CODEX_MONITORED_HEARTBEAT "$HEARTBEAT_SEC"
|
|
121
113
|
require_nonnegative_int CODEX_MONITORED_TIMEOUT_SEC "$TIMEOUT_SEC"
|
|
122
114
|
|
|
123
|
-
RECEIPT_ENABLED=""
|
|
124
|
-
for receipt_value in \
|
|
125
|
-
"${DEVLYN_INVOCATION_RUN_ID:-}" \
|
|
126
|
-
"${DEVLYN_INVOCATION_PHASE:-}" \
|
|
127
|
-
"${DEVLYN_INVOCATION_ROUND:-}" \
|
|
128
|
-
"${DEVLYN_INVOCATION_WORKDIR:-}" \
|
|
129
|
-
"${DEVLYN_INVOCATION_PROMPT_FILE:-}" \
|
|
130
|
-
"${DEVLYN_INVOCATION_SESSION_FILE:-}" \
|
|
131
|
-
"${DEVLYN_INVOCATION_RECEIPT:-}"; do
|
|
132
|
-
if [ -n "$receipt_value" ]; then
|
|
133
|
-
RECEIPT_ENABLED=1
|
|
134
|
-
fi
|
|
135
|
-
done
|
|
136
|
-
if [ -n "$RECEIPT_ENABLED" ]; then
|
|
137
|
-
for receipt_name in \
|
|
138
|
-
DEVLYN_INVOCATION_RUN_ID DEVLYN_INVOCATION_PHASE DEVLYN_INVOCATION_ROUND \
|
|
139
|
-
DEVLYN_INVOCATION_WORKDIR DEVLYN_INVOCATION_PROMPT_FILE \
|
|
140
|
-
DEVLYN_INVOCATION_SESSION_FILE DEVLYN_INVOCATION_RECEIPT; do
|
|
141
|
-
eval "receipt_value=\${$receipt_name:-}"
|
|
142
|
-
if [ -z "$receipt_value" ]; then
|
|
143
|
-
printf '[codex-monitored] error: %s is required for invocation receipt\n' \
|
|
144
|
-
"$receipt_name" >&2
|
|
145
|
-
exit 64
|
|
146
|
-
fi
|
|
147
|
-
done
|
|
148
|
-
require_nonnegative_int DEVLYN_INVOCATION_ROUND "$DEVLYN_INVOCATION_ROUND"
|
|
149
|
-
fi
|
|
150
|
-
|
|
151
|
-
if [ -n "${CODEX_MONITORED_ISOLATED:-}" ]; then
|
|
152
|
-
CODEX_ARGS=(
|
|
153
|
-
--ignore-user-config
|
|
154
|
-
--ignore-rules
|
|
155
|
-
--ephemeral
|
|
156
|
-
--disable codex_hooks
|
|
157
|
-
--disable hooks
|
|
158
|
-
"${CODEX_ARGS[@]}"
|
|
159
|
-
)
|
|
160
|
-
fi
|
|
161
|
-
|
|
162
115
|
# --- Pipe-stdout refusal (iter-0009 R2 finding #1) -------------------------
|
|
163
116
|
# `[ -p /dev/stdout ]` is the POSIX test for "is fd 1 a FIFO/pipe". Verified
|
|
164
117
|
# correct on macOS via lsof: distinguishes piped (`| cat`) from redirected
|
|
@@ -266,9 +219,6 @@ trap cleanup EXIT
|
|
|
266
219
|
|
|
267
220
|
printf '[codex-monitored] start: ts=%s heartbeat=%ds timeout=%ss bin=%s\n' \
|
|
268
221
|
"$(date -u +%FT%TZ)" "$HEARTBEAT_SEC" "$TIMEOUT_SEC" "$CODEX_BIN" >&2
|
|
269
|
-
if [ -n "${CODEX_MONITORED_ISOLATED:-}" ]; then
|
|
270
|
-
printf '[codex-monitored] isolated=1\n' >&2
|
|
271
|
-
fi
|
|
272
222
|
|
|
273
223
|
RECEIPT_HELPER="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/invocation-receipt.py"
|
|
274
224
|
# Native Python owns Windows PIDs and the timeout tree; MSYS PIDs are different.
|
|
@@ -304,7 +254,7 @@ if [ -n "$TIMEOUT_FLAG" ] && [ -f "$TIMEOUT_FLAG" ]; then
|
|
|
304
254
|
EXIT=124
|
|
305
255
|
fi
|
|
306
256
|
|
|
307
|
-
if [ -n "$
|
|
257
|
+
if [ -n "${DEVLYN_CODEX_PROMPT_FILE:-}" ]; then
|
|
308
258
|
python3 "$RECEIPT_HELPER" complete-dispatch --exit-code "$EXIT" || exit 64
|
|
309
259
|
fi
|
|
310
260
|
|
|
@@ -5,25 +5,18 @@
|
|
|
5
5
|
# just what's pinned. This script never writes .devlyn/engines.json, never
|
|
6
6
|
# installs anything, never changes pin validation — it only reports.
|
|
7
7
|
#
|
|
8
|
-
# CATALOG NOTE: `pi` (bin/devlyn.js's Pi/earendil-works install target) has
|
|
9
|
-
# no verified CLI binary name anywhere in this repo — its own installer
|
|
10
|
-
# detect() only checks project marker files, never shells out to a binary.
|
|
11
|
-
# Fabricating a `command -v pi` check would be a guess this repo's evidence
|
|
12
|
-
# rule forbids, so its binary column reports "unknown" rather than "no".
|
|
13
|
-
#
|
|
14
8
|
# macOS-safe bash 3.2: no associative arrays, no mapfile/readarray.
|
|
15
9
|
set -uo pipefail
|
|
16
10
|
|
|
17
11
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
|
|
18
12
|
ADAPTERS_DIR="$SCRIPT_DIR/adapters"
|
|
19
13
|
|
|
20
|
-
TARGETS=(claude codex
|
|
21
|
-
KINDS=(cli-engine cli-engine cli-engine
|
|
22
|
-
BINARIES=(claude codex
|
|
14
|
+
TARGETS=(claude codex omp pi)
|
|
15
|
+
KINDS=(cli-engine cli-engine cli-engine orchestrator-only)
|
|
16
|
+
BINARIES=(claude codex omp "")
|
|
23
17
|
INSTALL_HINTS=(
|
|
24
18
|
"see https://docs.anthropic.com/en/docs/claude-code"
|
|
25
19
|
"npm install -g @openai/codex"
|
|
26
|
-
"see https://docs.x.ai/build/overview (install + browser auth)"
|
|
27
20
|
"brew install can1357/tap/omp"
|
|
28
21
|
""
|
|
29
22
|
)
|
|
@@ -39,72 +32,39 @@ check_adapter() {
|
|
|
39
32
|
if [ -f "$ADAPTERS_DIR/$1.md" ]; then printf 'yes'; else printf 'no'; fi
|
|
40
33
|
}
|
|
41
34
|
|
|
42
|
-
|
|
43
|
-
# $1 = adapter file path.
|
|
44
|
-
#
|
|
45
|
-
# roles eligible by default (every adapter shipped before iter-0051).
|
|
35
|
+
check_executor() {
|
|
36
|
+
# $1 = adapter file path. An adapter may declare the fixed ASCII field
|
|
37
|
+
# `executor: no` under `## Role eligibility`; otherwise it can execute.
|
|
46
38
|
[ -f "$1" ] || { printf 'n/a'; return; }
|
|
47
|
-
|
|
48
|
-
grep -q '^executor: no' "$1" 2>/dev/null && exec_ok='no'
|
|
49
|
-
grep -q '^pair_judge: no' "$1" 2>/dev/null && judge_ok='no'
|
|
50
|
-
if [ "$exec_ok" = 'yes' ] && [ "$judge_ok" = 'yes' ]; then printf 'executor+judge'
|
|
51
|
-
elif [ "$judge_ok" = 'yes' ]; then printf 'judge-only'
|
|
52
|
-
elif [ "$exec_ok" = 'yes' ]; then printf 'executor-only'
|
|
53
|
-
else printf 'none'
|
|
54
|
-
fi
|
|
39
|
+
if grep -q '^executor: no' "$1" 2>/dev/null; then printf 'no'; else printf 'yes'; fi
|
|
55
40
|
}
|
|
56
41
|
|
|
57
|
-
printf '%-8s %-17s %-8s %-8s %-
|
|
58
|
-
'target' 'kind' 'binary' 'adapter' '
|
|
59
|
-
|
|
60
|
-
pin_eligible_count=0
|
|
61
|
-
missing_hints=()
|
|
42
|
+
printf '%-8s %-17s %-8s %-8s %-9s %s\n' \
|
|
43
|
+
'target' 'kind' 'binary' 'adapter' 'executor' 'note'
|
|
62
44
|
|
|
63
45
|
for i in "${!TARGETS[@]}"; do
|
|
64
46
|
target="${TARGETS[$i]}"
|
|
65
47
|
kind="${KINDS[$i]}"
|
|
66
48
|
binary="$(check_binary "${BINARIES[$i]}")"
|
|
67
49
|
adapter="$(check_adapter "$target")"
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
pin_eligible='no'
|
|
71
|
-
case "$kind" in
|
|
72
|
-
cli-engine) [ "$binary" = 'yes' ] && [ "$adapter" = 'yes' ] && pin_eligible='yes' ;;
|
|
73
|
-
esac
|
|
50
|
+
executor="$(check_executor "$ADAPTERS_DIR/$target.md")"
|
|
74
51
|
|
|
75
52
|
note='-'
|
|
76
53
|
case "$kind" in
|
|
77
54
|
cli-engine)
|
|
78
|
-
if [ "$
|
|
79
|
-
pin_eligible_count=$((pin_eligible_count + 1))
|
|
80
|
-
elif [ "$binary" = 'no' ]; then
|
|
55
|
+
if [ "$binary" = 'no' ]; then
|
|
81
56
|
note="not installed; ${INSTALL_HINTS[$i]}"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
57
|
+
elif [ "$adapter" = 'no' ]; then
|
|
58
|
+
note="binary present, no adapter — reinstall devlyn-cli, whose releases ship the adapters"
|
|
59
|
+
elif [ "$executor" = 'no' ]; then
|
|
60
|
+
note='adapter declares executor: no'
|
|
85
61
|
fi
|
|
86
62
|
;;
|
|
87
63
|
orchestrator-only)
|
|
88
|
-
note='informational only; not a routable
|
|
64
|
+
note='informational only; not a routable engine — no verified CLI binary or adapter'
|
|
89
65
|
;;
|
|
90
66
|
esac
|
|
91
67
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
printf '%-8s %-17s %-8s %-8s %-14s %-12s %s\n' \
|
|
95
|
-
"$target" "$kind" "$binary" "$adapter" "$role" "$pin_eligible" "$note"
|
|
68
|
+
printf '%-8s %-17s %-8s %-8s %-9s %s\n' \
|
|
69
|
+
"$target" "$kind" "$binary" "$adapter" "$executor" "$note"
|
|
96
70
|
done
|
|
97
|
-
|
|
98
|
-
printf '\n'
|
|
99
|
-
if [ "$pin_eligible_count" -lt 2 ]; then
|
|
100
|
-
printf 'Recommendation: only %d adapter-valid engine(s) available. VERIFY pair-judge and\n' "$pin_eligible_count"
|
|
101
|
-
printf 'risk-probe escalation need a second, genuinely different model to check against —\n'
|
|
102
|
-
printf 'iter-0045 found different model tiers hit different failure-mode blind spots (not\n'
|
|
103
|
-
printf 'that any arbitrary second model helps); with fewer than 2, those routes can never\n'
|
|
104
|
-
printf 'fire and every run stays solo. Add one of:\n'
|
|
105
|
-
for hint in "${missing_hints[@]}"; do
|
|
106
|
-
printf ' - %s\n' "$hint"
|
|
107
|
-
done
|
|
108
|
-
else
|
|
109
|
-
printf 'Pair-judge diversity: %d adapter-valid engines available.\n' "$pin_eligible_count"
|
|
110
|
-
fi
|
|
@@ -1,61 +1,29 @@
|
|
|
1
|
-
# Shared —
|
|
1
|
+
# Shared — Executor Pre-flight
|
|
2
2
|
|
|
3
|
-
Used by `/devlyn-
|
|
3
|
+
Used by `/devlyn-ideate` and `/devlyn-engines`: one rule for which engine executes implementation work and what happens when it is missing.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Selection
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
The executor is the `executor` pin in `.devlyn/engines.json` at the checkout root (set with `/devlyn-engines executor <name>`), else the CLI the user opened. A linked worktree or subdirectory has no pin unless one is set there. Pi and Grok have no executor adapter, so a drain from them needs a claude, codex or omp pin. Keys earlier releases wrote for retired pipeline roles (`pair_judge_priority`, `roles`) stay as written and select nothing: a worker profile never becomes the executor.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Before dispatching to the executor, run:
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
## Role resolution
|
|
15
|
-
|
|
16
|
-
The orchestrator is the CLI the user opened. PLAN remains orchestrator-fixed. Resolve the legacy executor as explicit `--engine` > `cwd/.devlyn/engines.json` `executor` > the invoking skill's supported default. BUILD_GATE runs commands in the orchestrator without a separate model; risk-probe derivation keeps its existing route.
|
|
17
|
-
|
|
18
|
-
The role profiles and `--role-config` below apply only to `/devlyn-resolve`; `/devlyn-ideate` keeps its existing `--engine` / legacy executor selection and never inherits worker/judge profiles. `role-config.py` is resolve’s deterministic resolver. Optional project `roles.worker`, `roles.primary_judge`, and `roles.pair_judge` each require `engine` and accept exact `model`/`effort`; `/devlyn-resolve --role-config <path>` supplies the same `{"roles": {...}}` object for one run. Each entry replaces the entire lower entry. Worker/primary precedence is run role > `--engine` engine-only > project role > legacy executor. Pair precedence is run role > project role > existing `pair_judge_priority`/binary complement. An absent primary profile follows the legacy executor, not the worker profile. No parent/global devlyn config lookup.
|
|
19
|
-
|
|
20
|
-
Worker controls IMPLEMENT, its included code/doc cleanup, and their repair rounds only; owner CLEANUP performs artifact/invariant checks without a worker dispatch; primary/pair control VERIFY only. OTHER must differ from the resolved primary engine. Explicit same-engine pair is invalid; a legacy priority list skips the primary. `--no-pair` does not dispatch or check availability of an unused pair. Configured pins remain explicit promises, while an unconfigured unavailable automatic OTHER records its solo reason.
|
|
21
|
-
|
|
22
|
-
Before the first phase, `/devlyn-resolve` freezes resolution with `state-phase-write.py --freeze-roles`; subsequent status/dispatch reads that state instead of mutable config. `state.engine` stays the legacy executor. The resolver rejects statically unsupported selected channel fields before PLAN, and validates adapter names and the existing ASCII `executor: yes|no` / `pair_judge: yes|no` eligibility markers; absent eligibility section means both. Malformed configuration fails with `BLOCKED:invalid-engine-config`.
|
|
23
|
-
|
|
24
|
-
Before each affected worker/judge, read `role-config.py --state .devlyn/pipeline.state.json --role <role> [--resolved-model <inherited-exact-model>]`. It returns source/channel, requested values and argv additions. Explicit Codex options require current version-matching native model metadata; refresh the native CLI's cache when missing/stale. Explicit Claude judge effort requires the adapter's version/exact-model capability declaration; model-only selection passes the exact ID without that effort declaration and validates the native result. No model/effort fields means unchanged phase defaults. Unknown support fails with `BLOCKED:unsupported-role-option`; no clamp, fallback or invented model identity. Native Agent workers reject unsupported explicit Claude fields without switching to a CLI worker. Show inherited/unknown values honestly; availability is not role fitness.
|
|
25
|
-
|
|
26
|
-
Engine-only adapter routes remain supported according to existing eligibility. New adapters do not automatically gain explicit model/effort mappings. Status reports unavailable pins and unresolved legacy channels without weakening run freeze/dispatch. CLI presence is not authentication. Status never launches a model, changes user configuration or promotes a default.
|
|
27
|
-
|
|
28
|
-
When a run or phase requires engine `<name>` (claude, codex, omp, or any adapter-backed engine), before spawning that phase:
|
|
11
|
+
```sh
|
|
12
|
+
python3 "$DEVLYN_SHARED_DIR/role-config.py" --workdir <project> --default-engine <this CLI> --select
|
|
13
|
+
```
|
|
29
14
|
|
|
30
|
-
|
|
31
|
-
2. On failure -> set the current phase/run verdict to `BLOCKED:<name>-unavailable`, preserve the failed check evidence, and show the adapter's setup guidance: install/configure that CLI, run its auth/login flow, verify `<name> --version`, then rerun. If the user intentionally wants solo VERIFY, they may rerun with `--no-pair`.
|
|
32
|
-
3. On success -> proceed with the original engine value.
|
|
15
|
+
A pin is a promise. When the pinned engine's CLI is unavailable, the command exits 1 with `BLOCKED:<engine>-unavailable`: stop, preserve the evidence, and show the setup guidance — install and authenticate that CLI, verify `<engine> --version`, rerun. Never substitute another engine. A malformed file, or a name without an executor-eligible adapter, is `BLOCKED:invalid-engine-config`. CLI presence is not authentication, and status never launches a model.
|
|
33
16
|
|
|
34
|
-
|
|
17
|
+
## Engines
|
|
35
18
|
|
|
36
|
-
|
|
19
|
+
An engine is pinnable when `_shared/adapters/<name>.md` exists and does not declare `executor: no` under `## Role eligibility`. Adapters ship with devlyn-cli releases, and a reinstall replaces `_shared`. An adapter's `## Invocation` says how to start it. On native Windows, use native Node/npm and Python (`python3` on PATH), with Git for Windows Bash for the shipped shell wrapper.
|
|
37
20
|
|
|
38
|
-
##
|
|
21
|
+
## Reporting a blocked engine
|
|
39
22
|
|
|
40
|
-
|
|
23
|
+
The final report names the selected engine, the verdict and the setup steps; it never reports a downgraded success:
|
|
41
24
|
|
|
42
25
|
```
|
|
43
|
-
Engine:
|
|
26
|
+
Engine: codex (pinned executor)
|
|
44
27
|
Verdict: BLOCKED:codex-unavailable
|
|
45
|
-
Setup: install/configure Codex CLI; run the current Codex auth/login flow; verify `codex --version`; rerun.
|
|
28
|
+
Setup: install/configure Codex CLI; run the current Codex auth/login flow; verify `codex --version`; rerun.
|
|
46
29
|
```
|
|
47
|
-
|
|
48
|
-
Do not report a downgraded successful run when a required engine is missing.
|
|
49
|
-
|
|
50
|
-
## Canonical cross-engine invocations
|
|
51
|
-
|
|
52
|
-
For native Windows, use native Node/npm and Python (`python3` on PATH), with Git for Windows Bash for the shipped shell wrapper. Supported npm engine shims resolve to native argv; unsupported/malformed shims fail visibly. Deliver multiline prompts from files: Codex uses `DEVLYN_CODEX_PROMPT_FILE` with explicit `-`; Claude uses `run-bounded.py 600 --stdin-file <prompt-file> --record-transport -- claude -p`. Retain generated transport evidence. A policy-denied Codex judge read uses the complete inline no-tools packet in `_shared/codex-config.md#constrained-windows-judge-reads`, keeping read-only isolation and the selected role.
|
|
53
|
-
|
|
54
|
-
After the availability check passes, Codex keeps the monitored `codex-monitored.sh`
|
|
55
|
-
path; every other resolved OTHER engine follows `_shared/adapters/<name>.md`
|
|
56
|
-
`## Invocation`. Capture `.devlyn/<name>-judge.stdout`, then normalize it with
|
|
57
|
-
`collect-codex-findings.py --devlyn-dir "<abs repo>/.devlyn" --stdout-file
|
|
58
|
-
<name>-judge.stdout`. A non-zero collector exit writes no canonical findings:
|
|
59
|
-
the pair source is `BLOCKED` for `verify.pair.emission-contract`, and unparsed
|
|
60
|
-
stdout remains diagnostic-only rather than being merged. On exit 124, the
|
|
61
|
-
timeout marker's `engine` field is the resolved `<name>`.
|