devlyn-cli 2.5.4 → 2.5.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/AGENTS.md +1 -1
- package/CLAUDE.md +1 -1
- package/benchmark/auto-resolve/scripts/run-fixture.sh +6 -2
- package/config/skills/_shared/engine-preflight.md +6 -1
- package/config/skills/_shared/verify-merge-findings.py +25 -2
- package/config/skills/devlyn:resolve/SKILL.md +15 -8
- package/config/skills/devlyn:resolve/references/phases/verify.md +16 -10
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -73,7 +73,7 @@ No silent fallbacks.
|
|
|
73
73
|
- Fallbacks allowed only when widely accepted and harmless (CSS fallback fonts, CDN failover, image placeholders).
|
|
74
74
|
- Silent `catch` blocks are bugs.
|
|
75
75
|
- Logging is not user-visible error handling.
|
|
76
|
-
- No engine-availability fallback
|
|
76
|
+
- No engine-availability fallback for EXPLICITLY-requested routes (`--engine`, `--risk-probes`, `--pair-verify`): if the required engine is unavailable, emit `BLOCKED:codex-unavailable` or `BLOCKED:claude-unavailable` with setup guidance; never downgrade an explicit route to solo. Automatic high-risk escalations (auto risk-probes / auto VERIFY pair) are capability-gated: they activate only when the OTHER engine is available, else the run proceeds solo and reports the skip (route selection, not a fallback — keeps single-LLM users first-class). `--no-pair` and `--no-risk-probes` remain explicit opt-outs.
|
|
77
77
|
|
|
78
78
|
## Evidence Over Claim
|
|
79
79
|
|
package/CLAUDE.md
CHANGED
|
@@ -6,7 +6,7 @@ devlyn-cli installs `/devlyn:ideate` (optional) and `/devlyn:resolve` (required)
|
|
|
6
6
|
|
|
7
7
|
Seven rules govern every change. Cite them by name when a decision touches one.
|
|
8
8
|
|
|
9
|
-
1. **No workaround** — fix the root cause, never the symptom. Blocked patterns: `any`, `@ts-ignore`, silent `catch`, hardcoded fallback hiding a broken contract, config-level skip bypassing the real issue, helper script that routes around it. **Permitted exceptions** (widely-accepted defaults only): CSS fallback fonts, CDN failover, image placeholders. **No engine-availability fallback** for `/devlyn:resolve`
|
|
9
|
+
1. **No workaround** — fix the root cause, never the symptom. Blocked patterns: `any`, `@ts-ignore`, silent `catch`, hardcoded fallback hiding a broken contract, config-level skip bypassing the real issue, helper script that routes around it. **Permitted exceptions** (widely-accepted defaults only): CSS fallback fonts, CDN failover, image placeholders. **No engine-availability fallback** for EXPLICITLY-requested `/devlyn:resolve` routes (`--engine`, `--risk-probes`, `--pair-verify`) — if the required engine is unavailable, stop with `BLOCKED:codex-unavailable` or `BLOCKED:claude-unavailable` and setup guidance; never downgrade an explicit route to solo. AUTOMATIC high-risk escalations (auto risk-probes / auto VERIFY pair) are capability-gated: they activate only when the OTHER engine is available; otherwise the run proceeds solo and reports the skip — route selection, not a fallback, and the reason single-LLM users stay first-class. `--no-pair` / `--no-risk-probes` remain explicit opt-outs.
|
|
10
10
|
2. **No overengineering** — smallest change that closes the goal. New abstractions require an observed failure mode they prevent. Subtractive-first: ask "what can I delete instead?" before writing anything new.
|
|
11
11
|
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.
|
|
12
12
|
4. **Worldclass** — code that survives review at a non-trivial codebase. Zero CRITICAL, zero HIGH security/design findings on the shippable path.
|
|
@@ -336,8 +336,12 @@ if [ "$ARM" = "variant" ] || [ "$ARM" = "solo_claude" ] \
|
|
|
336
336
|
|| [ "$ARM" = "l2_gated" ] || [ "$ARM" = "l2_risk_probes" ] || [ "$ARM" = "l2_forced" ]; then
|
|
337
337
|
case "$ARM" in
|
|
338
338
|
solo_claude)
|
|
339
|
-
|
|
340
|
-
|
|
339
|
+
# L1 = pure solo by construction. --no-risk-probes --no-pair so a high-risk
|
|
340
|
+
# spec measures Claude-only solo deterministically instead of auto-escalating
|
|
341
|
+
# toward the (blocked) OTHER engine. Without these, a security/high-risk
|
|
342
|
+
# fixture auto-triggers the cross-engine path and the arm mis-measures L1.
|
|
343
|
+
ENGINE_CLAUSE="--engine claude --no-risk-probes --no-pair"
|
|
344
|
+
ENGINE_PROMPT_HINT="Run with \`--engine claude --no-risk-probes --no-pair\` for every phase — pure solo L1. Codex must not be invoked — the harness has blocked it at the binary layer for this run."
|
|
341
345
|
;;
|
|
342
346
|
variant)
|
|
343
347
|
ENGINE_CLAUSE="--engine claude --risk-probes"
|
|
@@ -6,6 +6,11 @@ Used by `/devlyn:resolve` and `/devlyn:ideate`. One shared availability rule so
|
|
|
6
6
|
|
|
7
7
|
Each skill resolves the effective engine from its own SKILL.md default plus any explicit `--engine` flag passed by the user. `/devlyn:resolve` also computes conditional pair/risk-probe requirements before the phase that needs the OTHER engine.
|
|
8
8
|
|
|
9
|
+
Engine requirements have two classes:
|
|
10
|
+
|
|
11
|
+
- **Explicit routes** — `--engine`, `--risk-probes`, `--pair-verify`. These are promises. If the required engine is unavailable, fail closed with `BLOCKED:<engine>-unavailable` and never downgrade to solo.
|
|
12
|
+
- **Automatic escalations** — auto high-risk risk-probes and auto VERIFY pair-JUDGE inferred from the spec. These are candidate routes, selected only when their preconditions hold, OTHER-engine availability included. If an auto candidate would fire but the OTHER engine is absent, do not select the cross-engine route: proceed solo and report the skipped escalation and its reason. This is route selection, not a fallback.
|
|
13
|
+
|
|
9
14
|
When a run or phase requires Codex, before spawning that phase:
|
|
10
15
|
|
|
11
16
|
1. Check if the Codex CLI is installed: `command -v codex >/dev/null 2>&1` (or equivalent bash test).
|
|
@@ -18,7 +23,7 @@ When a run or phase requires Claude, before spawning that phase:
|
|
|
18
23
|
2. On failure -> set the current phase/run verdict to `BLOCKED:claude-unavailable` and show setup guidance: install/configure Claude Code, verify `claude --version` where available, then rerun.
|
|
19
24
|
3. On success -> proceed.
|
|
20
25
|
|
|
21
|
-
Never prompt the user mid-pipeline. Missing
|
|
26
|
+
Never prompt the user mid-pipeline. Missing engines for explicit routes are BLOCKED states, not silent fallbacks. Missing OTHER engines for automatic escalations are reported solo-skips, not fallbacks — the auto route was never selected.
|
|
22
27
|
|
|
23
28
|
Per-skill defaults: `/devlyn:resolve` uses Claude for PLAN/IMPLEMENT; VERIFY may invoke the OTHER engine when its pair-JUDGE trigger fires. `/devlyn:ideate` defaults to Claude; `--engine` selects the elicitation/normalization adapter, not an automatic cross-model challenge phase. Any future ideate read-only critique must follow `_shared/codex-config.md` isolation rules. Each SKILL.md flag block is source of truth for that skill's default.
|
|
24
29
|
|
|
@@ -33,7 +33,12 @@ VERDICT_RANK = {
|
|
|
33
33
|
"BLOCKED": 3,
|
|
34
34
|
}
|
|
35
35
|
RANK_VERDICT = {0: "PASS", 1: "PASS_WITH_ISSUES", 2: "NEEDS_WORK", 3: "BLOCKED"}
|
|
36
|
-
ALLOWED_PAIR_SKIP_REASONS = {
|
|
36
|
+
ALLOWED_PAIR_SKIP_REASONS = {
|
|
37
|
+
"user_no_pair",
|
|
38
|
+
"mechanical_blocker",
|
|
39
|
+
"primary_judge_blocker",
|
|
40
|
+
"auto_pair_other_engine_unavailable",
|
|
41
|
+
}
|
|
37
42
|
KNOWN_PAIR_TRIGGER_REASONS = {
|
|
38
43
|
"mode.verify-only",
|
|
39
44
|
"mode.pair-verify",
|
|
@@ -504,10 +509,28 @@ def pair_trigger_skip_contract_violation(
|
|
|
504
509
|
"id": "verify-pair-trigger-skipped-reason-unsupported",
|
|
505
510
|
"message": (
|
|
506
511
|
"pair_trigger.skipped_reason must be user_no_pair, "
|
|
507
|
-
"mechanical_blocker, primary_judge_blocker,
|
|
512
|
+
"mechanical_blocker, primary_judge_blocker, "
|
|
513
|
+
"auto_pair_other_engine_unavailable, or null."
|
|
508
514
|
),
|
|
509
515
|
"file": "pipeline.state.json",
|
|
510
516
|
}
|
|
517
|
+
if skipped_reason == "auto_pair_other_engine_unavailable":
|
|
518
|
+
# alpha+ capability-gating: an AUTOMATIC pair trigger may skip to solo
|
|
519
|
+
# VERIFY when the OTHER engine is unavailable (single-LLM users stay
|
|
520
|
+
# first-class). An EXPLICIT --pair-verify route is a promise and must
|
|
521
|
+
# BLOCK on an unavailable engine, never skip — enforce that here so the
|
|
522
|
+
# auto-skip cannot launder an explicit request.
|
|
523
|
+
pair_verify = state.get("pair_verify") if isinstance(state, dict) else None
|
|
524
|
+
if pair_verify is True:
|
|
525
|
+
return {
|
|
526
|
+
"id": "verify-pair-trigger-auto-skip-explicit-conflict",
|
|
527
|
+
"message": (
|
|
528
|
+
"pair_trigger skipped_reason auto_pair_other_engine_unavailable is "
|
|
529
|
+
"only valid for an automatic trigger; an explicit --pair-verify run "
|
|
530
|
+
"must BLOCK on an unavailable OTHER engine, not skip."
|
|
531
|
+
),
|
|
532
|
+
"file": "pipeline.state.json",
|
|
533
|
+
}
|
|
511
534
|
if skipped_reason == "user_no_pair":
|
|
512
535
|
risk_profile = state.get("risk_profile") if isinstance(state, dict) else {}
|
|
513
536
|
if not isinstance(risk_profile, dict) or risk_profile.get("pair_default_enabled") is not False:
|
|
@@ -17,7 +17,7 @@ Long-horizon agentic work; context auto-compacts. State lives in `.devlyn/pipeli
|
|
|
17
17
|
Hands-free. Measured by how far we get without human intervention.
|
|
18
18
|
|
|
19
19
|
1. Do not prompt the user mid-pipeline. When tempted to ask, pick the safe default, proceed, and log it in the final report.
|
|
20
|
-
2. Engine availability: follow `_shared/engine-preflight.md`.
|
|
20
|
+
2. Engine availability: follow `_shared/engine-preflight.md`. Two classes. **Explicit cross-engine routes** (`--engine`, `--risk-probes`, `--pair-verify`) are promises: if the required engine is unavailable, fail closed with `BLOCKED:<engine>-unavailable` and setup guidance — never downgrade an explicitly requested route to solo. **Automatic cross-engine escalations** (auto high-risk risk-probes, auto VERIFY pair-JUDGE) are candidate routes selected only when their preconditions hold, including OTHER-engine availability. If an auto candidate would fire but the OTHER engine is absent, do not select the cross-engine route — proceed solo and report the skipped escalation and its reason. Skipping an unselected optional route is route selection, not a fallback; an explicitly requested route is never skipped this way.
|
|
21
21
|
3. Phases run in declared order. No extra phases.
|
|
22
22
|
4. Orchestrator does not write code. It parses input, spawns phases, reads state, branches on verdicts, emits the report.
|
|
23
23
|
5. Continue by default. Halt only on (a) unrecoverable subagent failure, (b) IMPLEMENT producing zero code changes, (c) BUILD_GATE or VERIFY fix-loop exhausting `max_rounds`.
|
|
@@ -76,7 +76,7 @@ Once `state.implement_passed_sha` is non-null (PHASE 2 returned and produced a d
|
|
|
76
76
|
- **Spec**: validate spec exists. If sibling `spec.expected.json` exists, run `--check-expected <expected-path>` to validate both the expected contract, sibling spec `complexity` frontmatter, and any present actionable solo-headroom hypothesis; if the spec has a solo-headroom hypothesis, its observable command must match `spec.expected.json.verification_commands[].cmd`. Then stage `.devlyn/spec-verify.json` from `verification_commands`. Otherwise run `--check <spec-path>` to validate the legacy inline carrier plus supported `complexity` frontmatter and any present actionable solo-headroom hypothesis; if the spec uses an inline `## Verification` JSON carrier, any solo-headroom hypothesis command must match that carrier's `verification_commands[].cmd`. Then stage from the legacy inline carrier. Compute `state.source.spec_sha256`.
|
|
77
77
|
- **Verify-only**: skip to PHASE 5 with `state.source.spec_path` set, the supplied diff captured at `.devlyn/external-diff.patch`.
|
|
78
78
|
|
|
79
|
-
5. Compute `state.risk_profile` from the user goal plus spec/criteria text. Mark `high_risk: true` when the work touches any of: auth/authz, permissions, security, token/session, payment/money/billing/invoice/pricing/tax/ledger, persistence/data mutation/deletion/migration, idempotency/replay/duplicate, API/webhook/raw-body/signature, allocation/scheduling/inventory/rollback/transaction, or explicit error-priority/output-shape contracts. If high-risk and `--no-risk-probes` is absent, set `risk_probes_enabled: true
|
|
79
|
+
5. Compute `state.risk_profile` from the user goal plus spec/criteria text. Mark `high_risk: true` when the work touches any of: auth/authz, permissions, security, token/session, payment/money/billing/invoice/pricing/tax/ledger, persistence/data mutation/deletion/migration, idempotency/replay/duplicate, API/webhook/raw-body/signature, allocation/scheduling/inventory/rollback/transaction, or explicit error-priority/output-shape contracts. Explicit `--risk-probes` sets `risk_probes_enabled: true` unconditionally (the OTHER engine is then a promise; unavailability blocks at PHASE 1.5). If high-risk is detected and `--no-risk-probes` is absent, this is an AUTO escalation: set `risk_probes_enabled: true` only when the OTHER engine is available (for a Claude run, `command -v codex`); if it is unavailable, leave `risk_probes_enabled: false`, keep `high_risk: true`, and append a reason such as `auto-risk-probes skipped: codex-unavailable` so the report shows it. `--no-risk-probes` disables only the auto escalation; a single-engine run does not need it to proceed. If `--no-pair` is present, set `pair_default_enabled: false`. Add concise string reasons for the classification, but do not use reasons as substitutes for the boolean fields.
|
|
80
80
|
|
|
81
81
|
6. Announce one line: `resolve starting — run <run_id> — engine <engine> — mode <mode> — complexity <complexity-or-na> — pair <conditional|disabled> — risk_probes <on|off>`.
|
|
82
82
|
|
|
@@ -98,9 +98,12 @@ After return:
|
|
|
98
98
|
|
|
99
99
|
Skip unless `--risk-probes` is set OR `state.risk_profile.risk_probes_enabled`
|
|
100
100
|
is true. This phase is findings-as-executable-checks, not a second plan and not
|
|
101
|
-
debate.
|
|
101
|
+
debate. When it runs, the OTHER engine is required: if unavailable, halt with
|
|
102
102
|
`BLOCKED:codex-unavailable` or `BLOCKED:claude-unavailable` plus setup guidance;
|
|
103
|
-
do not silently continue without probes.
|
|
103
|
+
do not silently continue without probes. Reaching this halt means the route was
|
|
104
|
+
explicitly requested (`--risk-probes`) — an auto high-risk escalation toward an
|
|
105
|
+
unavailable OTHER engine was already gated off in PHASE 0 by leaving
|
|
106
|
+
`risk_probes_enabled: false`, so a single-engine high-risk run proceeds solo here.
|
|
104
107
|
|
|
105
108
|
Engine: OTHER engine from PHASE 2's selected IMPLEMENT engine. Prompt body:
|
|
106
109
|
`references/phases/probe-derive.md`.
|
|
@@ -245,10 +248,14 @@ After MECHANICAL and the primary JUDGE finish, compute `pair_trigger = { eligibl
|
|
|
245
248
|
|
|
246
249
|
The `--engine` flag never suppresses this rule. Explicit `--engine claude`
|
|
247
250
|
means "Claude is the primary judge"; it does not mean "do not run Codex as the
|
|
248
|
-
second pair judge." The
|
|
249
|
-
trigger are deterministic MECHANICAL HIGH/CRITICAL blockers
|
|
250
|
-
`--no-pair
|
|
251
|
-
|
|
251
|
+
second pair judge." The valid skip reasons after a non-empty eligible
|
|
252
|
+
trigger are deterministic MECHANICAL HIGH/CRITICAL blockers, an explicit
|
|
253
|
+
`--no-pair`, or — when none of the applicable reasons is `mode.pair-verify` (i.e.
|
|
254
|
+
every reason is automatic) — the OTHER engine being unavailable. In that
|
|
255
|
+
auto-only-unavailable case, set `eligible: false`, leave `reasons` empty, set
|
|
256
|
+
`skipped_reason: "auto_pair_other_engine_unavailable"`, and run solo VERIFY.
|
|
257
|
+
Explicit `--pair-verify` (`mode.pair-verify`) with the OTHER engine absent is a
|
|
258
|
+
promise: it stays `BLOCKED:<engine>-unavailable` and is never skipped.
|
|
252
259
|
|
|
253
260
|
Pair-mode JUDGE: spawn a second Agent with the OTHER engine's adapter; the second judge is a bounded adversarial complement, not a duplicate broad audit. The primary judge owns broad coverage; pair-JUDGE targets the two highest-risk explicit `## Verification` bullets that cross state mutation, all-or-nothing rollback, ordering, idempotency, auth, or error-priority clauses. If the spec includes a solo-headroom hypothesis, one of those targeted probes must exercise that hypothesis with the visible command/input shape and full externally visible result, using the hypothesis's backticked observable command as its command anchor before adding bounded input variations. It must not read `.claude/skills`, `.codex/skills`, `CLAUDE.md`, `AGENTS.md`, or other harness docs unless the orchestrator pasted a specific excerpt into the prompt. It may use only the spec, diff, implementation files, tests, and the repo's existing CLI/API/test runner. It may execute at most two targeted probes before first output, and each probe must compare the full externally visible result (exit/stdout/stderr plus full parsed output object, including accepted/scheduled rows, rejected rows, and remaining state when present), not just a single property. When the spec names exact keys, row shapes, JSON object shape, or an exact error body, pair-JUDGE must compare parsed key sets/deep equality so aliased keys, missing keys, and extra keys are verdict-binding failures, and it must construct inputs with the spec's visible key names. For priority/stateful specs, at least one probe must include an earlier input entity that would succeed under input-order processing, a later higher-priority entity that consumes or blocks the critical resource, and a failure/blocked/rollback edge that determines a later entity's state. For cart/pricing specs where visible verification combines duplicate items, line promotions, tax, coupon, and shipping, the success-path probe must include interleaved duplicates plus taxable and non-taxable items and assert full output rows. Scope qualifiers are binding: pair-JUDGE must not reinterpret `inside a warehouse`, `per resource`, or line-scoped rules as global rules. When both priority ordering and rollback/blocked-interval behavior appear in the spec, this dominance-loss probe is mandatory and comes before any other probe: an earlier lower-priority entity that would succeed alone or under input-order processing must lose because a later higher-priority entity is processed first; a failed/blocked middle entity must not corrupt later state; and the assertion must cover complete accepted/scheduled and rejected output ordering. It must stop and emit JSONL immediately on the first verdict-binding finding, and must emit PASS immediately if both probes plus static scope/dependency checks pass. Both judgments merge with the rule "any HIGH/CRITICAL finding either model surfaces is verdict-binding; high-confidence MEDIUM findings are also verdict-binding when they identify a concrete behavioral regression against the spec, public contract, or existing test contract." Cross-model disagreement on advisory lower-severity findings is logged but does not change the verdict. If MECHANICAL or the primary JUDGE has a verdict-binding finding, skip the second judge and record `pair_judge: null`; the fix loop needs the blocker, not duplicate review.
|
|
254
261
|
|
|
@@ -148,18 +148,24 @@ reasons and at least one reason: `mode.verify-only`, `complexity.high`, `complex
|
|
|
148
148
|
The `--engine` flag never disables this rule. Explicit `--engine claude` means
|
|
149
149
|
Claude is the primary judge; if pair-mode triggers, Codex is still the mandatory
|
|
150
150
|
OTHER-engine judge. Do not record "explicit --engine claude" as a skip reason.
|
|
151
|
-
The
|
|
152
|
-
MECHANICAL HIGH/CRITICAL blockers
|
|
153
|
-
|
|
151
|
+
The valid skip reasons after a non-empty eligible trigger are deterministic
|
|
152
|
+
MECHANICAL HIGH/CRITICAL blockers, an explicit `--no-pair`, or — when none of the
|
|
153
|
+
applicable reasons is `mode.pair-verify` (every reason is automatic) — the OTHER
|
|
154
|
+
engine being unavailable. Explicit `--pair-verify` with the OTHER engine absent
|
|
155
|
+
is a promise and stays `BLOCKED:<engine>-unavailable`, never a skip.
|
|
154
156
|
|
|
155
157
|
Before invoking the OTHER-engine judge, run the shared availability pre-flight
|
|
156
|
-
for that engine. If
|
|
157
|
-
`BLOCKED:codex-unavailable` and tell the user to
|
|
158
|
-
run the current Codex auth/login flow, verify
|
|
159
|
-
Claude is required and the host cannot spawn a
|
|
160
|
-
`BLOCKED:claude-unavailable` and tell the user to
|
|
161
|
-
verify `claude --version
|
|
162
|
-
solo pass
|
|
158
|
+
for that engine. If the route is explicit (`--pair-verify`) and Codex is required
|
|
159
|
+
and unavailable, set VERIFY to `BLOCKED:codex-unavailable` and tell the user to
|
|
160
|
+
install/configure the Codex CLI, run the current Codex auth/login flow, verify
|
|
161
|
+
`codex --version`, and rerun. If Claude is required and the host cannot spawn a
|
|
162
|
+
Claude agent, set VERIFY to `BLOCKED:claude-unavailable` and tell the user to
|
|
163
|
+
install/configure Claude Code, verify `claude --version`, and rerun. For an
|
|
164
|
+
explicit route, do not convert this to a solo pass and do not synthesize pair
|
|
165
|
+
findings. When the trigger was AUTO-only and the OTHER engine is unavailable, set
|
|
166
|
+
`eligible: false`, `reasons: []`, `skipped_reason:
|
|
167
|
+
"auto_pair_other_engine_unavailable"`, run solo VERIFY, and report the skipped
|
|
168
|
+
auto escalation — route selection, not a fallback.
|
|
163
169
|
|
|
164
170
|
When eligible and the orchestrator spawns a second VERIFY agent with the OTHER engine's adapter, both judgments are merged:
|
|
165
171
|
- Any HIGH/CRITICAL finding either model surfaces is verdict-binding.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devlyn-cli",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.5",
|
|
4
4
|
"description": "AI development toolkit for Claude Code — ideate, resolve, and ship with context engineering and agent orchestration",
|
|
5
5
|
"homepage": "https://github.com/fysoul17/devlyn-cli#readme",
|
|
6
6
|
"bin": {
|