devlyn-cli 2.7.0 → 2.8.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.
Files changed (78) hide show
  1. package/AGENTS.md +9 -1
  2. package/CLAUDE.md +28 -11
  3. package/benchmark/auto-resolve/fixtures/F1-cli-trivial-flag/task.ko.txt +8 -0
  4. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/NOTES.md +28 -0
  5. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/RETIRED.md +15 -0
  6. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/expected.json +57 -0
  7. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/metadata.json +10 -0
  8. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/setup.sh +50 -0
  9. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/spec.md +59 -0
  10. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/task.txt +9 -0
  11. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/verifiers/ordered-settlement.js +88 -0
  12. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/verifiers/reject-invalid.js +45 -0
  13. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/NOTES.md +30 -0
  14. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/RETIRED.md +16 -0
  15. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/expected.json +64 -0
  16. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/metadata.json +10 -0
  17. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/setup.sh +21 -0
  18. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/spec.md +58 -0
  19. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/task.txt +9 -0
  20. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/verifiers/dup-op.js +54 -0
  21. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/verifiers/priority-apply.js +82 -0
  22. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/verifiers/rollback.js +77 -0
  23. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/NOTES.md +99 -0
  24. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/RETIRED.md +18 -0
  25. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/expected.json +64 -0
  26. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/metadata.json +10 -0
  27. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/setup.sh +4 -0
  28. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/spec.md +61 -0
  29. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/task.txt +9 -0
  30. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/verifiers/correctness-small.js +135 -0
  31. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/verifiers/scale.js +100 -0
  32. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/verifiers/validation.js +75 -0
  33. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/NOTES.md +93 -0
  34. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/RETIRED.md +16 -0
  35. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/expected.json +65 -0
  36. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/metadata.json +10 -0
  37. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/setup.sh +18 -0
  38. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/spec.md +66 -0
  39. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/task.txt +9 -0
  40. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/verifiers/correctness-small.js +84 -0
  41. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/verifiers/scale.js +184 -0
  42. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/verifiers/validation.js +74 -0
  43. package/benchmark/auto-resolve/scripts/pair-rejected-fixtures.sh +12 -0
  44. package/benchmark/auto-resolve/scripts/run-fixture.sh +39 -1
  45. package/benchmark/auto-resolve/scripts/test-lint-fixtures.sh +1 -1
  46. package/bin/devlyn.js +42 -1
  47. package/config/skills/_shared/adapters/README.md +7 -0
  48. package/config/skills/_shared/adapters/claude.md +47 -0
  49. package/config/skills/_shared/adapters/omp.md +17 -0
  50. package/config/skills/_shared/archive_run.py +8 -2
  51. package/config/skills/_shared/codex-config.md +22 -6
  52. package/config/skills/_shared/engine-doctor.sh +107 -0
  53. package/config/skills/_shared/engine-preflight.md +18 -11
  54. package/config/skills/_shared/expected.schema.json +26 -0
  55. package/config/skills/_shared/runtime-principles.md +1 -1
  56. package/config/skills/_shared/spec-verify-check.py +773 -486
  57. package/config/skills/_shared/state-phase-write.py +374 -0
  58. package/config/skills/_shared/verify-merge-findings.py +177 -61
  59. package/config/skills/devlyn:engines/SKILL.md +47 -0
  60. package/config/skills/devlyn:ideate/SKILL.md +22 -3
  61. package/config/skills/devlyn:ideate/references/elicitation.md +2 -2
  62. package/config/skills/devlyn:ideate/references/from-spec-mode.md +2 -2
  63. package/config/skills/devlyn:ideate/references/project-mode.md +1 -1
  64. package/config/skills/devlyn:ideate/references/spec-template.md +15 -4
  65. package/config/skills/devlyn:queue/SKILL.md +26 -0
  66. package/config/skills/devlyn:resolve/SKILL.md +54 -24
  67. package/config/skills/devlyn:resolve/references/free-form-mode.md +1 -1
  68. package/config/skills/devlyn:resolve/references/phases/build-gate.md +2 -2
  69. package/config/skills/devlyn:resolve/references/phases/cleanup.md +1 -1
  70. package/config/skills/devlyn:resolve/references/phases/implement.md +1 -1
  71. package/config/skills/devlyn:resolve/references/phases/plan.md +3 -2
  72. package/config/skills/devlyn:resolve/references/phases/probe-derive.md +8 -0
  73. package/config/skills/devlyn:resolve/references/phases/verify.md +18 -9
  74. package/config/skills/devlyn:resolve/references/state-schema.md +10 -6
  75. package/optional-skills/devlyn:pencil-pull/SKILL.md +5 -0
  76. package/optional-skills/devlyn:pencil-push/SKILL.md +5 -0
  77. package/package.json +1 -1
  78. package/scripts/lint-skills.sh +48 -41
@@ -3,7 +3,7 @@ name: devlyn:resolve
3
3
  description: Hands-free pipeline for any coding task — bug fix, feature, refactor, debug, modify, PR review. Free-form goal or formal spec input. Plan → Implement → Build-gate → Cleanup → Verify (fresh subagent, findings-only). Mechanical-first verification; pair-mode is conditional-default in Verify. Use when the user says "resolve this", "fix this", "implement this", "refactor this", "debug this", "review this PR", or wants hands-off completion.
4
4
  ---
5
5
 
6
- Orchestrator for the 2-skill harness pipeline. One subagent per phase; file-based handoff via `.devlyn/pipeline.state.json`. VERIFY spawns a fresh-context subagent so independence is structural — not advisory.
6
+ Orchestrator for the 2-skill harness pipeline. One fresh worker per phase; file-based handoff via `.devlyn/pipeline.state.json`. VERIFY spawns a fresh-context worker so independence is structural — not advisory.
7
7
 
8
8
  <pipeline_config>
9
9
  $ARGUMENTS
@@ -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`. 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.
20
+ 2. Engine availability: follow `_shared/engine-preflight.md` for the explicit-route-vs-automatic-escalation distinction and BLOCKED-vs-skip behavior. Explicit routes (`--engine`, `--risk-probes`, `--pair-verify`) never downgrade to solo; unavailable auto-escalations proceed solo and report the skip.
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`.
@@ -27,12 +27,34 @@ Hands-free. Measured by how far we get without human intervention.
27
27
  Every phase reads `_shared/runtime-principles.md` (Subtractive-first / Goal-locked / No-workaround / Evidence). Codex routings receive the contract excerpt inlined in their prompt body.
28
28
  </harness_principles>
29
29
 
30
+ <runtime_paths>
31
+ Resolve shared scripts from this skill's installed directory, never from the project cwd. At PHASE 0, before any phase command:
32
+
33
+ ```bash
34
+ DEVLYN_SKILL_DIR="${CLAUDE_SKILL_DIR:-__DEVLYN_SKILL_DIR__}"
35
+ if [ "$DEVLYN_SKILL_DIR" = "__DEVLYN_SKILL_DIR__" ] || [ ! -d "$DEVLYN_SKILL_DIR/../_shared" ]; then
36
+ echo "BLOCKED:shared-dir-unresolved: $DEVLYN_SKILL_DIR/../_shared" >&2
37
+ exit 1
38
+ fi
39
+ DEVLYN_SHARED_DIR="$(cd "$DEVLYN_SKILL_DIR/../_shared" && pwd)"
40
+ CODEX_MONITORED_PATH="$DEVLYN_SHARED_DIR/codex-monitored.sh"
41
+ if [ ! -f "$CODEX_MONITORED_PATH" ]; then
42
+ echo "BLOCKED:shared-dir-unresolved: $CODEX_MONITORED_PATH" >&2
43
+ exit 1
44
+ fi
45
+ ```
46
+
47
+ `DEVLYN_SHARED_DIR` is the only valid `_shared` anchor. Claude Code supplies `CLAUDE_SKILL_DIR` by native render substitution; Codex/oh-my-pi installs receive an absolute copy-time stamp in the default branch. If the resolved skill directory is still the placeholder, `../_shared` is absent, or a required script is missing, halt with report-level `BLOCKED:shared-dir-unresolved` and include the failed path. Pass the absolute `DEVLYN_SHARED_DIR` and `CODEX_MONITORED_PATH` into every fresh phase worker.
48
+ </runtime_paths>
49
+
30
50
  <engine_routing>
31
51
  Each phase routes to an engine and prepends the per-engine adapter header from `_shared/adapters/<engine>.md` (e.g. `claude.md`, `codex.md`) to the canonical phase body. Adapter is the per-model delta (Anthropic's prompt-engineering guide for Claude, OpenAI's prompt guidance for Codex). Canonical body is engine-agnostic.
32
52
 
33
- - Claude phases: spawn `Agent` (`mode: "bypassPermissions"`); prompt = adapter-header + canonical-body + task-context.
34
- - Codex phases: shell out via `bash _shared/codex-monitored.sh` with the same compounded prompt. The wrapper closes stdin and emits a heartbeat. No MCP.
35
- - Default engine: Claude for PLAN / IMPLEMENT / BUILD_GATE / CLEANUP. `--engine codex` routes IMPLEMENT to Codex; orchestration stays Claude. Pair-mode is conditional-default in VERIFY/JUDGE and selects the OTHER engine for the fresh subagent when the trigger policy fires.
53
+ - Phase spawning is mandatory for every orchestrator. Same-context PLAN / IMPLEMENT / BUILD_GATE / CLEANUP / VERIFY is a contract violation. If the current CLI cannot spawn a fresh worker, write the current phase verdict as `"BLOCKED"` and report `BLOCKED:fresh-context-unavailable` with the failed spawn command; do not continue with ad-hoc same-context execution.
54
+ - Claude Code phases: spawn `Agent` (`mode: "bypassPermissions"`); prompt = adapter-header + canonical-body + task-context.
55
+ - Codex CLI phases: shell out via `bash "$CODEX_MONITORED_PATH"` with the same compounded prompt. Each `codex exec` child is a new session/fresh context. The wrapper closes stdin and emits a heartbeat. No MCP.
56
+ - oh-my-pi phases: spawn the native `task` tool with a fresh `context` containing adapter-header + canonical-body + task-context. Capture the task result into `.devlyn/<phase>.stdout` and any tool error into `.devlyn/<phase>.stderr` before updating state. If the `task` tool is unavailable for an omp-routed phase, write the current phase verdict as `"BLOCKED"` and report `BLOCKED:fresh-context-unavailable`; do not fall back to same-context execution or a nested `omp -p` subprocess.
57
+ - Default engine: Claude for PLAN / IMPLEMENT / BUILD_GATE / CLEANUP when the orchestrator has Claude Code's native `Agent` primitive; otherwise the default route is the current CLI's fresh worker (`codex` child process for Codex CLI, native `task` for oh-my-pi). Role resolution (`_shared/engine-preflight.md#role-resolution`): explicit `--engine` flag > machine-local `.devlyn/engines.json` `executor` pin > orchestrator-supported default above. Explicit routes and pins still fail closed when their required engine/spawn channel is unavailable. Pair-mode is conditional-default in VERIFY/JUDGE and selects the OTHER engine for the fresh worker when the trigger policy fires — OTHER = first adapter-valid, non-primary, available entry of `engines.json` `pair_judge_priority`, or the binary claude↔codex complement when unset.
36
58
  - Multi-LLM evolution: when a new model adapter ships in `_shared/adapters/`, that engine becomes selectable via `--engine <model>` without further skill changes (NORTH-STAR.md "Multi-LLM evolution direction").
37
59
  </engine_routing>
38
60
 
@@ -48,14 +70,14 @@ Three input shapes:
48
70
  Once `state.implement_passed_sha` is non-null (PHASE 2 returned and produced a diff), the post-IMPLEMENT phases (CLEANUP, VERIFY) operate under structural constraints:
49
71
 
50
72
  - CLEANUP may only mutate files in the cleanup allowlist (tooling artifacts, dead code added by this diff, doc references this diff invalidated). Other paths trigger revert.
51
- - VERIFY runs in a fresh subagent context with no code-mutation tools. Findings only — never edits files. The fresh-context spawn is the structural guarantee; the prompt body reinforces it but the spawn is what makes independence real.
73
+ - VERIFY runs in a fresh worker context with no code-mutation tools. Findings only — never edits files. The fresh-context spawn is the structural guarantee; the prompt body reinforces it but the spawn is what makes independence real. If no fresh worker can be spawned, VERIFY is `BLOCKED:fresh-context-unavailable`, never a same-context review.
52
74
  </post_implement_invariant>
53
75
 
54
76
  ## PHASE 0: PARSE + CLASSIFY + ROUTE
55
77
 
56
78
  1. Parse flags from `<pipeline_config>`:
57
79
  - `--max-rounds N` (default 4) — fix-loop budget shared across BUILD_GATE and VERIFY.
58
- - `--engine MODE` (default `claude`) — picks the adapter for IMPLEMENT, CLEANUP, and the primary VERIFY judge. It does not disable VERIFY pair-mode; when a VERIFY pair trigger fires, the second judge uses the OTHER engine.
80
+ - `--engine MODE` (default: `engines.json` `executor` pin when present, else `claude`) — picks the adapter for IMPLEMENT, CLEANUP, and the primary VERIFY judge. It does not disable VERIFY pair-mode; when a VERIFY pair trigger fires, the second judge uses the OTHER engine per role resolution.
59
81
  - `--spec <path>` — switches to spec mode.
60
82
  - `--verify-only <ref>` — switches to verify-only mode. Requires `--spec`.
61
83
  - `--goal-file <path>` — free-form mode: read the goal text from `<path>` (relative to cwd) instead of an inline positional goal. Used by the devlynd `ResolveAdapter` launcher (goal written to `.devlyn/goal.txt`, referenced by path — never inlined as argv). The file content is opaque goal text fed to the complexity classifier, **never re-parsed as flags**. Mutually exclusive with `--spec`, `--verify-only`, and an inline positional goal.
@@ -66,18 +88,18 @@ Once `state.implement_passed_sha` is non-null (PHASE 2 returned and produced a d
66
88
  - `--bypass <phase>[,...]` — skip specific phases. Valid: `build-gate`, `cleanup`. PLAN, IMPLEMENT, VERIFY are non-bypassable.
67
89
  - `--perf` — opt in to per-phase timing.
68
90
 
69
- 2. Engine pre-flight: follow `_shared/engine-preflight.md`. If a required engine is unavailable, halt with a BLOCKED verdict and setup instructions instead of downgrading.
91
+ 2. Engine pre-flight: follow `_shared/engine-preflight.md`, including role resolution. If `.devlyn/engines.json` exists: malformed JSON, any named engine without a `_shared/adapters/<name>.md` adapter, or an adapter that declares itself ineligible for the requested role, halts with report-level `BLOCKED:invalid-engine-config` naming the offending entry. A pin is an explicit route — a pinned but unavailable engine halts with `BLOCKED:<engine>-unavailable`. If a required engine is unavailable, halt with a BLOCKED verdict and setup instructions instead of downgrading.
70
92
 
71
93
  `--pair-verify` and `--no-pair` are mutually exclusive; if both are present, stop with `BLOCKED:invalid-flags`. Likewise `--goal-file` combined with `--spec`, `--verify-only`, or an inline positional goal — or a missing/repeated `--goal-file` value — stops with `BLOCKED:invalid-flags`. (These `BLOCKED:<reason>` strings are report-level only; the JSON `phases.*.verdict` carrier in `pipeline.state.json` always stays the bare enum `"BLOCKED"`.)
72
94
 
73
- 3. Initialize `.devlyn/pipeline.state.json` per `references/state-schema.md`. Set `state.run_id`, `started_at`, `engine`, `pair_verify: true` only when `--pair-verify` was passed and `false` otherwise, `base_ref.{branch, sha}`, `rounds.{max_rounds, global: 0}`, `bypasses`, empty `phases`, empty `criteria`, and `risk_profile: { high_risk: false, reasons: [], risk_probes_enabled: false, pair_default_enabled: true }`. `risk_profile` is strict typed state: keep it an object, keep the three flags as JSON booleans, and keep `reasons` as a string array; never serialize booleans as strings.
95
+ 3. Initialize `.devlyn/pipeline.state.json` per `references/state-schema.md`. Set `state.run_id`, `started_at`, `engine`, `engine_source` (`"flag"` | `"engines.json"` | `"default"`), `pair_verify: true` only when `--pair-verify` was passed and `false` otherwise, `base_ref.{branch, sha}`, `rounds.{max_rounds, global: 0}`, `bypasses`, empty `phases`, empty `criteria`, and `risk_profile: { high_risk: false, reasons: [], risk_probes_enabled: false, pair_default_enabled: true }`. `risk_profile` is strict typed state: keep it an object, keep the three flags as JSON booleans, and keep `reasons` as a string array; never serialize booleans as strings.
74
96
 
75
97
  4. **Mode-specific init**:
76
98
  - **Free-form**: first resolve `goal_text` — the inline positional goal, OR (when `--goal-file <path>` was passed) the raw UTF-8 content of `<path>`, read as opaque goal text (never re-parsed as flags). Fail closed before classifying: empty/whitespace-only file → `BLOCKED:goal-file-empty`; missing/unreadable/not-a-regular-file/invalid-UTF-8 → `BLOCKED:goal-file-unreadable`; an absolute path, a `..` segment, or a path that canonicalizes outside cwd → `BLOCKED:goal-file-invalid-path`. (Like all PHASE-0 halts these are report-level `BLOCKED:<reason>` strings — no `phases.*.verdict` is written, so the bare-enum carrier rule is never violated.) Then read `references/free-form-mode.md` and run the complexity classifier deterministically on `goal_text` (rules over keyword density / file count / spec-shape signals, plus pair-evidence intent). Set `state.complexity ∈ {trivial, medium, large}`. Trivial: write internal mini-spec to `.devlyn/criteria.generated.md` and proceed. Medium: synthesize a minimal spec from the goal + add 1-2 context anchors from the codebase, write to `.devlyn/criteria.generated.md`, proceed. Every free-form branch that writes criteria must set `state.source.type = "generated"`, `state.source.criteria_path = ".devlyn/criteria.generated.md"`, and `state.source.criteria_sha256` from the raw file bytes. Large: log `recommend: /devlyn:ideate first` in the final report and either halt (default) or proceed with assumed defaults if `--continue-on-large` flag set, except pair-evidence intent without an actionable solo-headroom hypothesis must halt with `BLOCKED:solo-headroom-hypothesis-required`, and unmeasured pair-candidate intent without solo ceiling avoidance must halt with `BLOCKED:solo-ceiling-avoidance-required`.
77
99
  - **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`.
78
100
  - **Verify-only**: skip to PHASE 5 with `state.source.spec_path` set, the supplied diff captured at `.devlyn/external-diff.patch`.
79
101
 
80
- 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.
102
+ 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 a pair-judge engine is available per role resolution (first adapter-valid, non-primary, available entry of `pair_judge_priority`; binary complement when unset — for a Claude run without a config, `command -v codex`); if none is available, 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.
81
103
 
82
104
  6. Announce one line: `resolve starting — run <run_id> — engine <engine> — mode <mode> — complexity <complexity-or-na> — pair <conditional|disabled> — risk_probes <on|off>`.
83
105
 
@@ -87,7 +109,7 @@ Skip in verify-only mode. The heaviest phase by design — spec/criteria define
87
109
 
88
110
  Engine: Claude (PLAN-pair is **unmeasured at HEAD** — iter-0033d is the first L1-vs-L2 measurement; iter-0020 falsified Codex-BUILD/IMPLEMENT, NOT PLAN-pair). Prompt body: `references/phases/plan.md`.
89
111
 
90
- Subagent output (writes `.devlyn/plan.md`): file list to touch, risk list (out-of-scope expansions, ambiguous spec sections), acceptance restatement (what `## Verification` actually requires verbatim).
112
+ Subagent output (writes `.devlyn/plan.md`): file list to touch, risk list (out-of-scope expansions, ambiguous spec sections), acceptance restatement (what `## Verification` actually requires verbatim). For large work only, PLAN may add a fourth section `## Execution phases` under the conditions in `references/phases/plan.md`; a phase block missing a runnable `gate:` line disqualifies the whole section — treat the run as single-phase and log the disqualification.
91
113
 
92
114
  State write: `phases.plan.{started_at, verdict, completed_at, duration_ms}`.
93
115
 
@@ -161,9 +183,9 @@ State write: `phases.probe_derive.{started_at, verdict, completed_at, duration_m
161
183
 
162
184
  Invocation contract when OTHER engine is Codex:
163
185
 
164
- - Invoke Codex only through the monitored wrapper path in `CODEX_MONITORED_PATH`,
165
- or `.claude/skills/_shared/codex-monitored.sh` when the env var is absent:
166
- `CODEX_MONITORED_ISOLATED=1 bash "$CODEX_MONITORED_PATH" -C "$PWD" --full-auto -c model_reasoning_effort=high "<probe prompt>"`.
186
+ - Invoke Codex only through the monitored wrapper path in `CODEX_MONITORED_PATH`
187
+ resolved from `DEVLYN_SHARED_DIR`:
188
+ `CODEX_MONITORED_ISOLATED=1 bash "$CODEX_MONITORED_PATH" -C "$PWD" -s workspace-write -c model_reasoning_effort=high "<probe prompt>"`.
167
189
  Isolation keeps user config, AGENTS.md, hooks, and project rules
168
190
  from adding hidden context, tool calls, or transcript side effects.
169
191
  - Do not run `codex`, `codex exec`, `/Users/.../codex`, or a plugin-provided
@@ -174,7 +196,7 @@ Invocation contract when OTHER engine is Codex:
174
196
  validating `.devlyn/risk-probes.jsonl`.
175
197
 
176
198
  After return:
177
- 1. Run `python3 .claude/skills/_shared/spec-verify-check.py --validate-risk-probes`
199
+ 1. Run `python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --validate-risk-probes`
178
200
  for the artifact boundary before IMPLEMENT; malformed probes halt with
179
201
  `BLOCKED:probe-derive-malformed`.
180
202
  2. IMPLEMENT receives `.devlyn/plan.md` plus `.devlyn/risk-probes.jsonl` as
@@ -189,11 +211,19 @@ Engine: per `--engine`. Prompt body: `references/phases/implement.md`.
189
211
 
190
212
  State write: `phases.implement.{started_at, verdict, completed_at, duration_ms}`.
191
213
 
192
- After return:
214
+ **Single-phase path** (plan.md has no `## Execution phases` section — every trivial/medium run, and any large run PLAN judged atomic): spawn IMPLEMENT once. No phase metadata of any kind appears in the prompt. After return:
193
215
  1. `git diff --stat` — empty diff → halt with `BLOCKED:implement-empty`.
194
216
  2. Set `state.implement_passed_sha = git rev-parse HEAD` (activates `<post_implement_invariant>`).
195
217
  3. Checkpoint: `git add -A && git commit -m "chore(pipeline): implement"`.
196
218
 
219
+ **Phase-gated path** (plan.md has `## Execution phases` with >1 phase): definitions are the contract in plan.md; progress is routing truth in `state.phases.implement.exec = { total, current, statuses, commits }` — never route on plan.md checkbox parsing. For each phase k = 1..N:
220
+ 1. Spawn IMPLEMENT with the standard prompt plus: this phase's plan.md block only, the current worktree as the working base (overrides the body's `base_ref.sha` framing after phase 1), a `git diff <base_ref.sha>...HEAD --stat` summary, and the prior phase's gate output.
221
+ 2. After return: run the phase's `gate:` commands directly — deterministic, exit-code truth, no LLM judgment.
222
+ 3. Gate PASS → `git add -A && git commit -m "chore(pipeline): implement phase <k>/<N>"`, write `exec.statuses[k-1] = "PASS"` + commit sha, advance `exec.current`, tick the plan.md checkbox mirror (display only).
223
+ 4. Gate FAIL → no commit. One fix respawn for this phase with the gate output (increments `rounds.global`, shares `max_rounds`); second FAIL → `exec.statuses[k-1] = "FAIL"`, halt with `BLOCKED:phase-gate-exhausted`.
224
+
225
+ After the final phase's gate PASS: `git diff <base_ref.sha>...HEAD --stat` — empty → halt with `BLOCKED:implement-empty`; otherwise set `state.implement_passed_sha = git rev-parse HEAD` (phase commits already checkpoint the work — no extra commit).
226
+
197
227
  ## PHASE 3: BUILD_GATE
198
228
 
199
229
  Skip in verify-only mode OR when `build-gate` in `state.bypasses`. Deterministic — same commands CI / Docker / production run.
@@ -201,7 +231,7 @@ Skip in verify-only mode OR when `build-gate` in `state.bypasses`. Deterministic
201
231
  Spawn Claude `Agent` (`mode: "bypassPermissions"`) with prompt body `references/phases/build-gate.md`. The agent:
202
232
  1. Detects language/framework via project files (`package.json`, `pyproject.toml`, etc.).
203
233
  2. Runs language-specific gates (tsc / lint / test).
204
- 3. Always runs `python3 .claude/skills/_shared/spec-verify-check.py --include-risk-probes` (verification_commands literal-match plus `.devlyn/risk-probes.jsonl` when present). If `state.risk_profile.risk_probes_enabled == true`, the script requires `.devlyn/risk-probes.jsonl`; a missing file is a CRITICAL mechanical blocker, not a silent solo run.
234
+ 3. Always runs `python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --include-risk-probes` (verification_commands literal-match plus `.devlyn/risk-probes.jsonl` when present). If `state.risk_profile.risk_probes_enabled == true`, the script requires `.devlyn/risk-probes.jsonl`; a missing file is a CRITICAL mechanical blocker, not a silent solo run.
205
235
  4. If diff touches web-surface files: run the browser tier with the repo's available toolchain (for example Playwright or curl).
206
236
  5. Emits `.devlyn/build_gate.findings.jsonl` + `.devlyn/build_gate.log.md`.
207
237
 
@@ -220,7 +250,7 @@ Engine: per `--engine`. Prompt body: `references/phases/cleanup.md`. Allowlist e
220
250
  - Dead code added by this diff (not pre-existing dead code).
221
251
  - Doc references whose target this diff renamed or removed.
222
252
 
223
- Before spawn: capture `state.phases.cleanup.pre_sha = git rev-parse HEAD`.
253
+ Before spawn: pass `--pre-sha "$(git rev-parse HEAD)"` to the `state-phase-write.py ... spawn` call for this phase.
224
254
 
225
255
  State write: `phases.cleanup.{started_at, verdict, completed_at, duration_ms}`.
226
256
 
@@ -234,7 +264,7 @@ Independent quality layer. **Spawned with empty conversation context** — no ca
234
264
 
235
265
  Two sub-phases:
236
266
 
237
- 1. **MECHANICAL** (deterministic): re-run `SPEC_VERIFY_PHASE=verify_mechanical SPEC_VERIFY_FINDINGS_FILE=verify-mechanical.findings.jsonl SPEC_VERIFY_FINDING_PREFIX=VERIFY-MECH python3 .claude/skills/_shared/spec-verify-check.py --include-risk-probes` against the post-CLEANUP code (independent of BUILD_GATE's earlier run). If `state.risk_profile.risk_probes_enabled == true`, missing `.devlyn/risk-probes.jsonl` is a CRITICAL mechanical blocker. This emits `.devlyn/verify-mechanical.findings.jsonl` for `verify-merge-findings.py`.
267
+ 1. **MECHANICAL** (deterministic): re-run `SPEC_VERIFY_PHASE=verify_mechanical SPEC_VERIFY_FINDINGS_FILE=verify-mechanical.findings.jsonl SPEC_VERIFY_FINDING_PREFIX=VERIFY-MECH python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --include-risk-probes` against the post-CLEANUP code (independent of BUILD_GATE's earlier run). If `state.risk_profile.risk_probes_enabled == true`, missing `.devlyn/risk-probes.jsonl` is a CRITICAL mechanical blocker. This emits `.devlyn/verify-mechanical.findings.jsonl` for `verify-merge-findings.py`.
238
268
 
239
269
  2. **JUDGE** (fresh-context Agent): grade the diff against the spec on rubric axes (spec compliance, scope, quality, consistency). Split each Requirement into binding clauses and trace code-order counterexamples; a passing verifier proves only the case it exercises, not neighboring `once` / `regardless` / `duplicate` / auth-order / rollback invariants. Respect scope qualifiers such as `inside a warehouse`, `per resource`, `for this line`, and `after validation`; do not widen a scoped clause into a global invariant, and compose multiple ordering rules in the stated order. For stateful flows, explicitly trace failed-operation rollback and the next entity's state before hunting broader edge cases. For high-complexity specs, construct at least one interaction counterexample that combines ordering/priority with failure handling and state mutation, then execute at least one such scenario through the repo's existing CLI/API/test runner without leaving tracked files behind; one-axis examples and pure mental tracing are insufficient. Default engine = same as IMPLEMENT (solo). Pair-mode (cross-model JUDGE) is eligible only after MECHANICAL and the primary JUDGE have no verdict-binding findings; deterministic blockers and primary JUDGE blockers already decide the verdict and route to the fix loop. Pair-mode fires when eligible and:
240
270
  - `--pair-verify` flag set, OR
@@ -247,8 +277,8 @@ Two sub-phases:
247
277
 
248
278
  After MECHANICAL and the primary JUDGE finish, compute `pair_trigger = { eligible, reasons[], skipped_reason }`, write it into `state.phases.verify`, and then spawn the second OTHER-engine judge when eligible. If `eligible == true`, `reasons` must be non-empty, include every applicable canonical reason, and every reason must be one of these canonical values: `mode.verify-only`, `mode.pair-verify`, `complexity.high`, `complexity.large`, `spec.complexity.high`, `spec.complexity.large`, `spec.solo_headroom_hypothesis`, `risk.high`, `risk_probes.enabled`, `risk_probes.present`, `coverage.failed`, `mechanical.warning`, or `judge.warning`; `skipped_reason` must be null; and you MUST spawn the second OTHER-engine judge. If `eligible == false`, `reasons` must be empty and `skipped_reason` must be a string or null. Contradictory, incomplete, or unknown trigger state is a VERIFY contract violation, not advisory metadata; `verify-merge-findings.py` blocks malformed trigger state. Pair reasons derive `risk.high` and `risk_probes.enabled` from `state.risk_profile`; malformed `risk_profile` is also a VERIFY contract violation because it can hide a required pair decision.
249
279
 
250
- The `--engine` flag never suppresses this rule. Explicit `--engine claude`
251
- means "Claude is the primary judge"; it does not mean "do not run Codex as the
280
+ The `--engine` flag never suppresses this rule. An explicit `--engine <name>`
281
+ names the primary judge; it does not mean "do not run the OTHER engine as the
252
282
  second pair judge." The valid skip reasons after a non-empty eligible
253
283
  trigger are deterministic MECHANICAL HIGH/CRITICAL blockers, an explicit
254
284
  `--no-pair`, or — when none of the applicable reasons is `mode.pair-verify` (i.e.
@@ -270,7 +300,7 @@ failed availability check evidence, and print setup guidance:
270
300
  - Claude: install/configure Claude Code, run `claude --version` when available,
271
301
  confirm the host can spawn Claude agents, then rerun.
272
302
 
273
- Findings written to `.devlyn/verify.findings.jsonl`. **VERIFY agents have no code-mutation tools.** Codex pair-JUDGE is read-only: invoke `codex-monitored.sh` with `CODEX_MONITORED_ISOLATED=1` and `-c model_reasoning_effort=medium`, no `tail`/`head`/`grep` pipes, direct stdout/stderr capture, JSONL findings on stdout, and orchestrator-written `.devlyn/verify.pair.findings.jsonl`. Isolation blocks user config, AGENTS.md, hooks, and project rules from hidden context/tool/transcript side effects. If stdout is captured as `.devlyn/codex-judge.stdout`, run `python3 .claude/skills/_shared/collect-codex-findings.py` before merge; raw stdout is diagnostic only. If stdout contains findings or a non-PASS summary while `.devlyn/verify.pair.findings.jsonl` is empty, `verify-merge-findings.py` blocks VERIFY for `verify.pair.emission-contract`. Do not ask Codex to `apply_patch` or edit `.devlyn`. After primary and pair findings are written, run `python3 .claude/skills/_shared/verify-merge-findings.py --write-state`. Branch only on the merged `state.phases.verify.verdict`; a HIGH/CRITICAL finding from either judge must mechanically become `NEEDS_WORK`. Never write `.devlyn/verify-merged.findings.jsonl` or `.devlyn/verify-merge.summary.json` by hand; `verify-merge-findings.py` is their only writer. State write: `phases.verify.{started_at, verdict, completed_at, duration_ms, sub_verdicts: {mechanical, judge, pair_judge?}, artifacts}`.
303
+ Findings written to `.devlyn/verify.findings.jsonl`. **VERIFY agents have no code-mutation tools.** Codex pair-JUDGE is read-only: invoke `codex-monitored.sh` with `CODEX_MONITORED_ISOLATED=1` and `-c model_reasoning_effort=medium`, no `tail`/`head`/`grep` pipes, direct stdout/stderr capture, JSONL findings on stdout, and orchestrator-written `.devlyn/verify.pair.findings.jsonl`. Isolation blocks user config, AGENTS.md, hooks, and project rules from hidden context/tool/transcript side effects. If stdout is captured as `.devlyn/codex-judge.stdout`, run `python3 "$DEVLYN_SHARED_DIR/collect-codex-findings.py"` before merge; raw stdout is diagnostic only. If stdout contains findings or a non-PASS summary while `.devlyn/verify.pair.findings.jsonl` is empty, `verify-merge-findings.py` blocks VERIFY for `verify.pair.emission-contract`. Do not ask Codex to `apply_patch` or edit `.devlyn`. When the OTHER engine is Claude (codex/omp orchestrator), the judge call follows `_shared/adapters/claude.md` `## Invocation` (headless `claude -p`, `dontAsk` allowlist, hermetic settings) with stdout captured as `.devlyn/claude-judge.stdout`; the same emission contract applies to that file. After primary and pair findings are written, run `python3 "$DEVLYN_SHARED_DIR/verify-merge-findings.py" --write-state`. Branch only on the merged `state.phases.verify.verdict`; a HIGH/CRITICAL finding from either judge must mechanically become `NEEDS_WORK`. Never write `.devlyn/verify-merged.findings.jsonl` or `.devlyn/verify-merge.summary.json` by hand; `verify-merge-findings.py` is their only writer. State write: `phases.verify.{started_at, verdict, completed_at, duration_ms, sub_verdicts: {mechanical, judge, pair_judge?}, artifacts}`.
274
304
 
275
305
  Branch:
276
306
  - `PASS` → PHASE 6.
@@ -287,10 +317,10 @@ State write: `phases.final_report.started_at` at the top of this phase.
287
317
 
288
318
  3. State write: `phases.final_report.{verdict, completed_at, duration_ms}` BEFORE archive runs (archive prune logic skips runs whose `final_report.verdict` is null).
289
319
 
290
- 4. **Archive** — invoke the deterministic script: `python3 .claude/skills/_shared/archive_run.py`. The script reads `run_id` from `.devlyn/pipeline.state.json`, moves per-run artifacts (state.json + `*.findings.jsonl` + `*.log.md` + `fix-batch.round-*.json` + `criteria.generated.md` + `risk-probes.jsonl` + `spec-verify*.json` + `spec-verify-findings.jsonl`) into `.devlyn/runs/<run_id>/`, then best-effort prunes to last 10 completed runs. Archive must run; running this step as deterministic-script-not-prose ensures the move actually happens (iter-0033a Smoke 3 caught a case where the agent claimed archive ran without moving the files).
320
+ 4. **Archive** — invoke the deterministic script: `python3 "$DEVLYN_SHARED_DIR/archive_run.py"`. The script reads `run_id` from `.devlyn/pipeline.state.json`, moves the per-run artifact set (`PER_RUN_PATTERNS` in the script is the single source of truth) into `.devlyn/runs/<run_id>/`, then best-effort prunes to last 10 completed runs. Archive must run; running this step as deterministic-script-not-prose ensures the move actually happens (iter-0033a Smoke 3 caught a case where the agent claimed archive ran without moving the files).
291
321
 
292
322
  5. Kill any dev server PHASE 3 left running.
293
323
 
294
324
  ## State management
295
325
 
296
- `.devlyn/pipeline.state.json` is the single authoritative verdict source. Branch on `state.phases.<name>.verdict` directly; never parse `.devlyn/*.findings.jsonl` for routing decisions. Schema: `references/state-schema.md`.
326
+ `.devlyn/pipeline.state.json` is the single authoritative verdict source. Branch on `state.phases.<name>.verdict` directly; never parse `.devlyn/*.findings.jsonl` for routing decisions. Schema and write protocol: `references/state-schema.md`. Every "State write: `phases.X.{...}`" note above describes the resulting shape only — the orchestrator always writes it via `state-phase-write.py spawn`/`complete` (`references/state-schema.md#write-protocol`); phase workers never edit `pipeline.state.json` directly.
@@ -74,7 +74,7 @@ When the rules are silent (rare — pathological goal text), default to `medium`
74
74
  The internal mini-spec written for trivial / medium / `--continue-on-large` paths must satisfy:
75
75
 
76
76
  - `## Requirements` non-empty, each bullet testable (CLI command, test command, observable file change).
77
- - `## Verification` non-empty if the goal implies any runnable acceptance check. Empty Verification is allowed only when all Requirements are pure-design (e.g. "follow existing pattern X").
77
+ - `## Verification` is preceded by a `<!-- devlyn:verification -->` sentinel on its own line directly above the heading — the machine locator `spec-verify-check.py` uses; the heading text itself is decorative and may be any language. `## Verification` non-empty if the goal implies any runnable acceptance check. Empty Verification is allowed only when all Requirements are pure-design (e.g. "follow existing pattern X").
78
78
  - If a free-form goal includes pair-evidence intent and already includes an actionable solo-headroom hypothesis, preserve that literal hypothesis in `.devlyn/criteria.generated.md` unchanged enough for VERIFY to detect `solo-headroom hypothesis`, `solo_claude`, `miss`, and the backticked observable command line that itself contains `miss`, emit the canonical `spec.solo_headroom_hypothesis` pair trigger reason, and satisfy regenerated-evidence checks such as `benchmark audit --require-hypothesis-trigger`.
79
79
  - If a free-form goal includes unmeasured pair-candidate intent and already includes solo ceiling avoidance, preserve that literal note in `.devlyn/criteria.generated.md` unchanged enough for reviewers to see `solo ceiling avoidance`, `solo_claude`, and the concrete difference from rejected or solo-saturated controls such as `S2`-`S6`.
80
80
  - Free-form mode mini-specs are written to `.devlyn/criteria.generated.md` (not to a roadmap path) — this is run-scoped artifact, not a documented spec.
@@ -22,7 +22,7 @@ Run in this order; each emits findings into `.devlyn/build_gate.findings.jsonl`:
22
22
  1. **Type check** (TypeScript / mypy / etc.). Each error → one finding, severity `HIGH`, rule `correctness.type-check`.
23
23
  2. **Lint** (eslint / ruff / clippy / etc.). Each error → finding, severity `MEDIUM`, rule `quality.lint`. Warnings stay LOW unless the spec elevates them.
24
24
  3. **Test suite** (npm test / pytest / go test / cargo test). Each failing test → finding, severity `HIGH`, rule `correctness.test-failure`. Include the failing test's file:line and the assertion.
25
- 4. **Spec literal verification + risk probes**: `python3 .claude/skills/_shared/spec-verify-check.py --include-risk-probes`. The script self-stages from sibling `spec.expected.json` next to `state.source.spec_path`, or the legacy inline carrier when the sibling is absent; benchmark-prestaged `.devlyn/spec-verify.json` still wins. It appends `.devlyn/risk-probes.jsonl` when present, and requires that file when `state.risk_profile.risk_probes_enabled == true`. Malformed `state.risk_profile` is also CRITICAL because it can hide enabled risk probes. Command or risk-probe mismatch → CRITICAL finding. Missing required risk probes, missing/malformed generated carrier, or malformed sibling expected file → `correctness.spec-verify-malformed` CRITICAL.
25
+ 4. **Spec literal verification + risk probes**: `python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --include-risk-probes`. The script self-stages from sibling `spec.expected.json` next to `state.source.spec_path`, or the legacy inline carrier when the sibling is absent; benchmark-prestaged `.devlyn/spec-verify.json` still wins. It appends `.devlyn/risk-probes.jsonl` when present, and requires that file when `state.risk_profile.risk_probes_enabled == true`. Malformed `state.risk_profile` is also CRITICAL because it can hide enabled risk probes. Command or risk-probe mismatch → CRITICAL finding. Missing required risk probes, missing/malformed generated carrier, or malformed sibling expected file → `correctness.spec-verify-malformed` CRITICAL. The same invocation also enforces PLAN's declared `authorized_surface` (the json block under `.devlyn/plan.md`'s `<!-- devlyn:authorized-surface -->` section, located by that sentinel regardless of the heading's language) against this run's diff: any changed path outside the declared surface → `scope.out-of-scope-file` CRITICAL — the only sanctioned fix is removing the file from the diff, never widening the declared surface, since that would let the same worker that leaked scope self-authorize the leak; missing sentinel or malformed `plan.md`/its surface block → `scope.authorized-surface-malformed` CRITICAL. This scope check runs only here (BUILD_GATE), not when VERIFY MECHANICAL reuses this script post-CLEANUP, because CLEANUP's own allowlist legitimately touches paths PLAN never declared.
26
26
  5. **Browser** (only when diff touches `*.tsx`, `*.jsx`, `*.vue`, `*.svelte`, `page.*`, `layout.*`, `route.*`, `*.css`, `*.html`): start the dev server and run the repo's existing browser checks, or a minimal curl/HTML check when no browser test harness exists. Each failed check → finding, severity `HIGH`, rule `correctness.browser-flow-failed`.
27
27
 
28
28
  Append all findings; do not stop on the first failure.
@@ -31,7 +31,7 @@ Append all findings; do not stop on the first failure.
31
31
  <output>
32
32
  - `.devlyn/build_gate.findings.jsonl` — JSONL stream, one finding per line. Schema: `{id, rule_id, severity, file, line, message, fix_hint, criterion_ref}`.
33
33
  - `.devlyn/build_gate.log.md` — human-readable summary of which gates ran and their raw output.
34
- - `state.phases.build_gate.{verdict, completed_at, duration_ms, artifacts}`. Verdict: `PASS` if zero CRITICAL/HIGH findings; `FAIL` otherwise.
34
+ - Report your verdict via these artifacts: `PASS` if zero CRITICAL/HIGH findings; `FAIL` otherwise. Do not edit `pipeline.state.json` yourself — the orchestrator records it via `state-phase-write.py` from these artifacts.
35
35
  </output>
36
36
 
37
37
  <quality_bar>
@@ -25,7 +25,7 @@ Files outside this allowlist must not change. Pre-existing tooling leaks (alread
25
25
 
26
26
  <output>
27
27
  - Code changes within the allowlist.
28
- - `state.phases.cleanup.{verdict, completed_at, duration_ms}`. Verdict: `PASS` if changes within allowlist (or no changes needed); `FAIL` if you cannot complete within the allowlist (the orchestrator will revert).
28
+ - Report your verdict in this reply: `PASS` if changes within allowlist (or no changes needed); `FAIL` if you cannot complete within the allowlist (the orchestrator will revert). Do not edit `pipeline.state.json` yourself — the orchestrator records it via `state-phase-write.py`.
29
29
  </output>
30
30
 
31
31
  <quality_bar>
@@ -16,7 +16,7 @@ You execute the plan. Constrained design judgment within PLAN's invariants — w
16
16
  - Code changes implementing every Requirement. Verify with `git diff`.
17
17
  - Tests added or updated for changed behavior. Run the full test suite before stopping.
18
18
  - For each criterion satisfied, set `state.criteria[i].status: "implemented"` with an `evidence` record `{"file": "...", "line": N, "note": "brief"}`.
19
- - `state.phases.implement.{verdict, completed_at, duration_ms}`. Verdict: `PASS` on success; `BLOCKED` if a criterion cannot be satisfied (missing external dep, blocking ambiguity in the spec) — never silently `pending`.
19
+ - Report your verdict in this reply: `PASS` on success; `BLOCKED` if a criterion cannot be satisfied (missing external dep, blocking ambiguity in the spec) — never silently `pending`. Do not edit `pipeline.state.json` yourself — the orchestrator records it via `state-phase-write.py`.
20
20
  </output>
21
21
 
22
22
  <quality_bar>
@@ -15,11 +15,12 @@ You translate a spec or generated criteria into a concrete plan: the file list t
15
15
  <output>
16
16
  Write `.devlyn/plan.md` with three sections:
17
17
 
18
- 1. **Files to touch** — explicit list. Each entry: path, change type (`new` / `edit` / `delete`), one-line rationale tied to a specific Requirement.
18
+ 1. **Files to touch** — precede this section with a `<!-- devlyn:authorized-surface -->` sentinel comment on its own line directly above the heading (the machine locator BUILD_GATE uses; the heading text itself is decorative, any language). Explicit list: each entry is a path, change type (`new` / `edit` / `delete`), one-line rationale tied to a specific Requirement. Immediately after the list, emit one fenced ```json block restating just the paths as the mechanical scope contract BUILD_GATE enforces: `{"authorized_surface": ["path/one.ts", "path/two.ts"]}`. An entry may end in `/**` to authorize an entire directory only when the file count is genuinely unenumerable (e.g. a codemod) — this is your scoping judgment, not a mechanism default. List every path from section 1 literally; do not paraphrase or add paths not already decided above.
19
19
  2. **Risks** — out-of-scope expansions to refuse, ambiguous spec sections to interpret strictly, known failure modes for this language/framework.
20
20
  3. **Acceptance restatement** — verbatim copy of the spec's `## Verification` block (or generated criteria's equivalent). The plan is wrong if any verification command later fails because of a planning oversight.
21
+ 4. **Execution phases** — conditional; large work only. Emit this section ONLY when ALL hold: (a) spec frontmatter `complexity: high` (legacy `large`) or `state.complexity == "large"`; (b) the work spans multiple subsystems or more than ~8 files; (c) every phase boundary has at least one runnable gate command. Otherwise emit exactly the three sections above — when in doubt, do not decompose. When emitted: 2-5 `### Phase <k> — <title>` blocks, each with task checkboxes (`- [ ]`), a `gate:` line (1-2 commands, exit-code truth, runnable at that boundary), and the files it owns (a subset of section 1). Definitions are written once here and are the contract; the orchestrator later updates checkboxes as a display mirror only — routing truth lives in `pipeline.state.json`.
21
22
 
22
- Also update `pipeline.state.json:phases.plan.{verdict, completed_at, duration_ms}`. Verdict: `PASS` if plan is shippable; `BLOCKED` if spec is internally contradictory or cannot be planned without violating constraints.
23
+ Report your verdict in this reply: `PASS` if plan is shippable; `BLOCKED` if spec is internally contradictory or cannot be planned without violating constraints. Do not edit `pipeline.state.json` yourself — the orchestrator records it via `state-phase-write.py`.
23
24
  </output>
24
25
 
25
26
  <quality_bar>
@@ -11,6 +11,14 @@ second planner, critic essay, or debate participant. Your output is JSONL only.
11
11
  - Source spec or generated criteria.
12
12
  - `.devlyn/plan.md`.
13
13
  - Codebase read/search at `state.base_ref.sha`.
14
+ - If the spec declares `required_risk_probe_requirements` (a language-neutral
15
+ `{tag, derived_from}` obligation list — see `_shared/expected.schema.json`),
16
+ the orchestrator pastes just that array into this prompt, never the rest of
17
+ `spec.expected.json`. Every `derived_from` value is already an exact
18
+ substring of the visible `## Verification` text you read directly, so this
19
+ exposes nothing hidden. Emit at least one probe per declared entry whose
20
+ `tags` includes that `tag` and whose `derived_from` matches the declared
21
+ value verbatim — BUILD_GATE mechanically checks this coverage.
14
22
  </input>
15
23
 
16
24
  <forbidden_input>
@@ -1,6 +1,6 @@
1
- # PHASE 5 — VERIFY (canonical body, fresh subagent context)
1
+ # PHASE 5 — VERIFY (canonical body, fresh worker context)
2
2
 
3
- Per-engine adapter header is prepended at runtime. **You are spawned with empty conversation context.** No carry-over from PLAN / IMPLEMENT / BUILD_GATE / CLEANUP. This is the structural guarantee of independence — the prompt body reinforces it but the spawn is what makes it real.
3
+ Per-engine adapter header is prepended at runtime. **You are spawned with empty conversation context.** No carry-over from PLAN / IMPLEMENT / BUILD_GATE / CLEANUP. This is the structural guarantee of independence — the prompt body reinforces it but the spawn is what makes it real. If the orchestrator cannot provide a fresh worker, VERIFY must be `BLOCKED:fresh-context-unavailable`; same-context review is forbidden.
4
4
 
5
5
  <role>
6
6
  Independent quality layer. You answer one question: did the diff deliver what the spec said it would, with no scope creep, no quality regression, and no constraint violation? You produce findings only — you have no code-mutation tools.
@@ -21,7 +21,7 @@ You do NOT receive: PLAN, IMPLEMENT's reasoning, BUILD_GATE's findings, CLEANUP'
21
21
 
22
22
  Re-run the mechanical checks fresh, independent of BUILD_GATE's earlier run:
23
23
 
24
- 1. `SPEC_VERIFY_PHASE=verify_mechanical SPEC_VERIFY_FINDINGS_FILE=verify-mechanical.findings.jsonl SPEC_VERIFY_FINDING_PREFIX=VERIFY-MECH python3 .claude/skills/_shared/spec-verify-check.py --include-risk-probes` against the post-CLEANUP code. In spec mode, sibling `spec.expected.json` wins; a malformed sibling is CRITICAL, not a fallback. When `state.risk_profile.risk_probes_enabled == true`, missing `.devlyn/risk-probes.jsonl` is also CRITICAL. The script also checks `forbidden_patterns`, `required_files`, `forbidden_files`, and `max_deps_added`.
24
+ 1. `SPEC_VERIFY_PHASE=verify_mechanical SPEC_VERIFY_FINDINGS_FILE=verify-mechanical.findings.jsonl SPEC_VERIFY_FINDING_PREFIX=VERIFY-MECH python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --include-risk-probes` against the post-CLEANUP code. In spec mode, sibling `spec.expected.json` wins; a malformed sibling is CRITICAL, not a fallback. When `state.risk_profile.risk_probes_enabled == true`, missing `.devlyn/risk-probes.jsonl` is also CRITICAL. The script also checks `forbidden_patterns`, `required_files`, `forbidden_files`, and `max_deps_added`.
25
25
 
26
26
  Emit findings to `.devlyn/verify-mechanical.findings.jsonl`. Each match = one finding. Severity from the pattern's `severity` field (disqualifier → CRITICAL, warning → MEDIUM).
27
27
 
@@ -145,9 +145,11 @@ reasons and at least one reason: `mode.verify-only`, `complexity.high`, `complex
145
145
  `risk_probes.present`, `coverage.failed`, `mechanical.warning`, or
146
146
  `judge.warning`.
147
147
 
148
- The `--engine` flag never disables this rule. Explicit `--engine claude` means
149
- Claude is the primary judge; if pair-mode triggers, Codex is still the mandatory
150
- OTHER-engine judge. Do not record "explicit --engine claude" as a skip reason.
148
+ The `--engine` flag never disables this rule. An explicit `--engine <name>`
149
+ names the primary judge; if pair-mode triggers, the first available OTHER
150
+ engine is still the mandatory pair judge (role resolution:
151
+ `_shared/engine-preflight.md`). Do not record "explicit --engine <name>" as a
152
+ skip reason.
151
153
  The valid skip reasons after a non-empty eligible trigger are deterministic
152
154
  MECHANICAL HIGH/CRITICAL blockers, an explicit `--no-pair`, or — when none of the
153
155
  applicable reasons is `mode.pair-verify` (every reason is automatic) — the OTHER
@@ -227,11 +229,18 @@ side effects. Do not pipe it to `tail`, `head`, `grep`, `sed`, or `awk`.
227
229
  Capture stdout/stderr directly. The Codex judge must return JSONL findings on
228
230
  stdout; the orchestrator writes `.devlyn/verify.pair.findings.jsonl` and merges
229
231
  verdicts. Do not ask Codex to `apply_patch` or edit `.devlyn`.
232
+ When the OTHER engine is Claude (codex/omp orchestrator), the judge call
233
+ follows `_shared/adapters/claude.md` `## Invocation`: headless `claude -p`
234
+ with `--permission-mode dontAsk`, an allowlist of `Read,Grep,Glob` plus the
235
+ repo test command, hermetic settings, stdout captured to
236
+ `.devlyn/claude-judge.stdout`. The same bounded-output contract and emission
237
+ rule apply to that stdout file, and the orchestrator writes the same
238
+ canonical `.devlyn/verify.pair.findings.jsonl`.
230
239
  The Codex prompt must include a bounded-output contract: no harness-doc reads,
231
240
  maximum two targeted probes before first output, stop on the first
232
241
  verdict-binding finding, and emit PASS immediately after the bounded checks pass.
233
242
  If stdout is first captured to `.devlyn/codex-judge.stdout`, run
234
- `python3 .claude/skills/_shared/collect-codex-findings.py` before merge. That
243
+ `python3 "$DEVLYN_SHARED_DIR/collect-codex-findings.py"` before merge. That
235
244
  script is the deterministic boundary writer for
236
245
  `.devlyn/verify.pair.findings.jsonl`.
237
246
  If raw Codex stdout is captured as `.devlyn/codex-judge.stdout`,
@@ -243,7 +252,7 @@ silently recover from a broken capture contract.
243
252
  After all VERIFY findings files are written, run:
244
253
 
245
254
  ```bash
246
- python3 .claude/skills/_shared/verify-merge-findings.py --write-state
255
+ python3 "$DEVLYN_SHARED_DIR/verify-merge-findings.py" --write-state
247
256
  ```
248
257
 
249
258
  This deterministic merge is the routing source of truth for VERIFY. It writes
@@ -264,7 +273,7 @@ VERIFY to `BLOCKED`; do not synthesize merge artifacts in prose.
264
273
  - `.devlyn/verify-mechanical.findings.jsonl` — MECHANICAL findings.
265
274
  - `.devlyn/verify.findings.jsonl` — JUDGE findings.
266
275
  - `.devlyn/verify-merged.findings.jsonl` and `.devlyn/verify-merge.summary.json` — deterministic merge artifacts.
267
- - `state.phases.verify.{verdict, completed_at, duration_ms, sub_verdicts: {mechanical, judge, pair_judge?}, merged, artifacts}`. Verdict: result of `verify-merge-findings.py`. `PASS` requires zero CRITICAL/HIGH findings, zero verdict-binding MEDIUM regressions, and coverage met.
276
+ - `phases.verify.{verdict, sub_verdicts, merged}` are written by `verify-merge-findings.py --write-state`, never by you (VERIFY agents have no code-mutation tools). `completed_at`/`duration_ms`/`artifacts` are recorded by the orchestrator via `state-phase-write.py` after this phase returns. `PASS` requires zero CRITICAL/HIGH findings, zero verdict-binding MEDIUM regressions, and coverage met.
268
277
  </output>
269
278
 
270
279
  <quality_bar>
@@ -47,16 +47,18 @@ Single authoritative verdict source for `/devlyn:resolve`. The orchestrator bran
47
47
  - **mode** — `"free-form" | "spec" | "verify-only"`.
48
48
  - **pair_verify** — boolean. Set true only when the user passed `--pair-verify`; otherwise false. This is the durable state evidence for the `mode.pair-verify` pair-trigger reason. It is mutually exclusive with `risk_profile.pair_default_enabled == false` from `--no-pair`; `verify-merge-findings.py` blocks the contradictory state.
49
49
  - **complexity** — `null | "trivial" | "medium" | "large"`. Free-form mode populates this; spec/verify-only mode leaves it null.
50
- - **engine** — `"claude" | "codex" | "auto"` initially; a required unavailable engine stops the run with `BLOCKED:<engine>-unavailable`.
50
+ - **engine** — any engine name with a shipped adapter (`_shared/adapters/<name>.md`); `"claude"` and `"codex"` today. A required unavailable engine stops the run with `BLOCKED:<engine>-unavailable`.
51
+ - **engine_source** — `"flag" | "engines.json" | "default"` — provenance of the resolved executor engine (`_shared/engine-preflight.md#role-resolution`). Optional on archived pre-iter-0038 runs; absent means `default`.
51
52
  - **source** — provenance for the contract all downstream phases read. Spec and verify-only mode set `type: "spec"`, `spec_path`, and `spec_sha256`. Free-form mode sets `type: "generated"`, leaves `spec_path`/`spec_sha256` null, and must set `criteria_path: ".devlyn/criteria.generated.md"` plus `criteria_sha256` from the generated file's raw bytes. VERIFY re-checks the matching hash before judging.
52
53
  - **risk_profile** — PHASE 0 classification for conditional defaults. `high_risk` records durable-risk signals from the goal/spec; `risk_probes_enabled` is true for explicit `--risk-probes` or high-risk specs unless `--no-risk-probes`; `pair_default_enabled` is false only for explicit `--no-pair`. `risk_profile` must remain an object with boolean `high_risk`, `risk_probes_enabled`, and `pair_default_enabled` fields when present, plus `reasons` as a list of strings. Malformed `risk_profile` blocks VERIFY because pair-trigger reasons derive `risk.high` and `risk_probes.enabled` from this state.
53
- - **rounds.global** — incremented every fix-loop pass (BUILD_GATE → fix-loop OR VERIFY → fix-loop).
54
+ - **rounds.global** — incremented every fix-loop pass (BUILD_GATE → fix-loop, VERIFY → fix-loop, OR a phase-gate fix respawn inside phase-gated IMPLEMENT).
55
+ - **phases.implement.exec** — only on phase-gated large runs (plan.md has `## Execution phases` with >1 phase): `{ "total": N, "current": k, "statuses": ["PASS"|"FAIL"|null, ...], "commits": ["<sha>", ...] }`. Phase definitions live in plan.md (immutable contract); progress lives here (routing truth — never route on plan.md checkboxes). Absent on single-phase runs. `cumulative.patch` for such runs is `git diff <base_ref.sha>...HEAD`.
54
56
  - **phases.probe_derive** — optional PHASE 1.5 entry when `--risk-probes` is enabled. Artifacts include `.devlyn/risk-probes.jsonl`. Probe failures later surface through BUILD_GATE/VERIFY as `correctness.risk-probe-failed`.
55
57
  - **bypasses** — array of phase names from `--bypass`. Valid: `"build-gate" | "cleanup"`. PLAN, IMPLEMENT, VERIFY are non-bypassable (orchestrator rejects at parse time).
56
58
  - **implement_passed_sha** — captured at end of PHASE 2; null until then. Activates the post-implement invariant for CLEANUP and VERIFY.
57
59
  - **criteria** — generated from spec's `## Requirements` checklist (one per `- [ ]`). `status: pending → implemented` is the legal transition. `failed_by_finding_ids` populates when VERIFY surfaces a finding tied to a criterion.
58
60
  - **verify.coverage_failed** — set by VERIFY's JUDGE sub-phase when a spec axis could not be exercised against the diff. Triggers pair-mode escalation when set. Pair-mode also triggers for `state.pair_verify == true`, verify-only mode, high-risk specs, active risk probes, actionable solo-headroom hypotheses, `complexity: high` specs, or current free-form `state.complexity` of `"large"` when MECHANICAL and the primary JUDGE have no verdict-binding blockers. Legacy/external spec `complexity: large` remains accepted for compatibility; new specs use `high`. Legacy `"high"` state remains accepted by the merge validator only for archived run compatibility.
59
- - **verify.pair_trigger** — VERIFY's trigger decision: `{ "eligible": boolean, "reasons": string[], "skipped_reason": string|null }`. The shape is strict: `eligible: true` requires a non-empty reasons list containing every applicable canonical eligible reason and only canonical eligible reasons, plus `skipped_reason: null`; `eligible: false` requires an empty reasons list and may set only `user_no_pair`, `mechanical_blocker`, `primary_judge_blocker`, or null as the skip cause. Canonical eligible reasons are `mode.verify-only`, `mode.pair-verify`, `complexity.high`, `complexity.large`, `spec.complexity.high`, `spec.complexity.large`, `spec.solo_headroom_hypothesis`, `risk.high`, `risk_probes.enabled`, `risk_probes.present`, `coverage.failed`, `mechanical.warning`, and `judge.warning`. `user_no_pair` is valid only when `risk_profile.pair_default_enabled == false` from an explicit `--no-pair`; `mechanical_blocker` and `primary_judge_blocker` are valid only when the matching source has a verdict-binding finding. If state implies a pair decision is required but `pair_trigger` is missing, if it records `eligible:false` with no supported skip reason, if an eligible trigger omits an applicable reason such as `spec.solo_headroom_hypothesis`, or if any combination is malformed, `verify-merge-findings.py` blocks VERIFY.
61
+ - **verify.pair_trigger** — VERIFY's trigger decision: `{ "eligible": boolean, "reasons": string[], "skipped_reason": string|null }`. The shape is strict: `eligible: true` requires a non-empty reasons list containing every applicable canonical eligible reason and only canonical eligible reasons, plus `skipped_reason: null`; `eligible: false` requires an empty reasons list and may set only `user_no_pair`, `mechanical_blocker`, `primary_judge_blocker`, `auto_pair_other_engine_unavailable`, or null as the skip cause. Canonical eligible reasons are `mode.verify-only`, `mode.pair-verify`, `complexity.high`, `complexity.large`, `spec.complexity.high`, `spec.complexity.large`, `spec.solo_headroom_hypothesis`, `risk.high`, `risk_probes.enabled`, `risk_probes.present`, `coverage.failed`, `mechanical.warning`, and `judge.warning`. `user_no_pair` is valid only when `risk_profile.pair_default_enabled == false` from an explicit `--no-pair`; `mechanical_blocker` and `primary_judge_blocker` are valid only when the matching source has a verdict-binding finding; `auto_pair_other_engine_unavailable` is valid only when every applicable trigger reason is automatic (none is `mode.pair-verify`) and no pair-judge engine is available per role resolution (pre-iter-0038 SKILL.md already used this skip; the schema previously omitted it — reconciled). If state implies a pair decision is required but `pair_trigger` is missing, if it records `eligible:false` with no supported skip reason, if an eligible trigger omits an applicable reason such as `spec.solo_headroom_hypothesis`, or if any combination is malformed, `verify-merge-findings.py` blocks VERIFY.
60
62
 
61
63
  ## Per-phase shape
62
64
 
@@ -88,8 +90,10 @@ Each entry under `phases.<name>` (for `plan`, `probe_derive`, `implement`, `buil
88
90
 
89
91
  ## Write protocol
90
92
 
91
- 1. **Before each phase spawn**: orchestrator writes `phases.<name>.{started_at, round, triggered_by}` and (when applicable) `pre_sha`.
92
- 2. **After each agent returns**: orchestrator validates `verdict`, `completed_at`, `duration_ms`, `artifacts` are populated. Missing fields → orchestrator fills from observable state. Branching on a null verdict is undefined behavior.
93
+ Phase lifecycle (`started_at`/`completed_at`/`duration_ms`/`round`/`triggered_by`/`verdict`) is written by a deterministic script, never hand-edited JSON — a hand-edited fix-loop respawn left `started_at` stale in iter-0042 (`autoresearch/iterations/0042-compliance-drift-probes.md`), corrupting cross-phase ordering because `completed_at`/`round`/`triggered_by` advanced to the new round while `started_at` didn't. Phase workers report their verdict and artifact paths in their reply; they never edit `pipeline.state.json` themselves.
94
+
95
+ 1. **Spawn** (before every phase dispatch, including a fix-loop respawn — one call per outer phase-gated IMPLEMENT run, not per inner sub-phase): `python3 "$DEVLYN_SHARED_DIR/state-phase-write.py" --devlyn-dir .devlyn --phase <name> spawn --round <N> [--triggered-by build_gate|verify] [--pre-sha <sha>] [--engine <e>] [--model <m>]`. Resets `started_at` to now and nulls `completed_at`/`duration_ms`/`verdict`/`artifacts`/`sub_verdicts` — a respawn can never inherit a prior round's stale timing or verdict. A VERIFY spawn also deletes the prior round's on-disk VERIFY artifacts (`verify*.jsonl`, `verify-merge.summary.json`, `*-judge.*`) so a stale pair findings file can never read as current-round spawn evidence in `verify-merge-findings.py`. Fields the script doesn't own (e.g. `phases.implement.exec`, the phase-gated progress tracker) are left untouched.
96
+ 2. **Complete** (after the orchestrator reads the phase's reply and determines the verdict): `python3 "$DEVLYN_SHARED_DIR/state-phase-write.py" --devlyn-dir .devlyn --phase <name> complete --verdict <V> [--findings-file <path>] [--log-file <path>]`. `duration_ms` is derived from the phase's own recorded `started_at` — it cannot drift from `completed_at`. `--verdict` is required for every phase except VERIFY: `verify-merge-findings.py --write-state` is the sole writer of `phases.verify.verdict`, so `complete verify` is always called without `--verdict` (an explicit one is rejected) and preserves what's already on disk.
93
97
  3. **Before archive** (PHASE 6 step 3): `phases.final_report.verdict` must be non-null. Archive prune skips runs whose final_report verdict is null (treated as in-flight).
94
98
 
95
99
  ## Terminal verdict (PHASE 6)
@@ -114,4 +118,4 @@ Follow-up notes: any `--continue-on-large` assumptions, pair/risk-probe opt-out
114
118
 
115
119
  ## Archive contract
116
120
 
117
- PHASE 6 step 4 moves `.devlyn/*` (excluding `.devlyn/runs/`) into `.devlyn/runs/<run_id>/`. The `.devlyn/runs/` directory keeps the last 10 completed runs (sorted by `started_at`). Best-effort prune; archive failure does not change the run's verdict.
121
+ PHASE 6 step 4 moves the per-run artifact set (`PER_RUN_PATTERNS` in `archive_run.py` — the single source of truth) into `.devlyn/runs/<run_id>/`. Machine config such as `.devlyn/engines.json` is not a run artifact and stays in place. The `.devlyn/runs/` directory keeps the last 10 completed runs (sorted by `started_at`). Best-effort prune; archive failure does not change the run's verdict.
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: devlyn:pencil-pull
3
+ description: Pull Pencil designs into code with exact visual fidelity.
4
+ ---
5
+
1
6
  # Pull Pencil Design into Code
2
7
 
3
8
  Implement the selected Pencil design in code with exact visual fidelity. Work through one component at a time, verifying each against the design before moving on.
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: devlyn:pencil-push
3
+ description: Push codebase UI to Pencil canvas for design sync.
4
+ ---
5
+
1
6
  # Push Codebase Design to Pencil
2
7
 
3
8
  Read the current UI implementation and recreate it as a matching design in Pencil, so the .pen canvas accurately reflects what's live in code.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devlyn-cli",
3
- "version": "2.7.0",
3
+ "version": "2.8.0",
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": {