devlyn-cli 4.0.1 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/AGENTS.md +11 -95
  2. package/CLAUDE.md +7 -160
  3. package/LICENSE +21 -0
  4. package/README.md +83 -114
  5. package/bin/devlyn.js +330 -472
  6. package/bin/instruction-templates.json +120 -2
  7. package/bin/instructions.js +35 -8
  8. package/config/skills/_shared/adapters/claude.md +8 -85
  9. package/config/skills/_shared/adapters/codex.md +14 -34
  10. package/config/skills/_shared/adapters/omp.md +1 -11
  11. package/config/skills/_shared/codex-monitored.sh +4 -54
  12. package/config/skills/_shared/engine-doctor.sh +18 -58
  13. package/config/skills/_shared/engine-preflight.md +15 -47
  14. package/config/skills/_shared/expected-contract.py +242 -0
  15. package/config/skills/_shared/expected.schema.json +10 -95
  16. package/config/skills/_shared/invocation-receipt.py +26 -757
  17. package/config/skills/_shared/platform-support.py +37 -28
  18. package/config/skills/_shared/role-config.py +100 -421
  19. package/config/skills/_shared/runtime-principles.md +17 -93
  20. package/config/skills/_shared/task-complete.py +490 -285
  21. package/config/skills/_shared/task-completion.md +235 -0
  22. package/config/skills/devlyn-engines/SKILL.md +9 -11
  23. package/config/skills/devlyn-ideate/SKILL.md +57 -107
  24. package/config/skills/devlyn-ideate/references/elicitation.md +18 -99
  25. package/config/skills/devlyn-ideate/references/loop.md +110 -0
  26. package/config/skills/devlyn-ideate/references/package-format.md +128 -0
  27. package/config/skills/devlyn-ideate/scripts/acceptance.py +795 -0
  28. package/config/skills/devlyn-ideate/scripts/queue.py +1780 -0
  29. package/package.json +2 -3
  30. package/config/commit-conventions.md +0 -28
  31. package/config/skills/_shared/adapters/README.md +0 -74
  32. package/config/skills/_shared/adapters/grok.md +0 -100
  33. package/config/skills/_shared/archive_run.py +0 -969
  34. package/config/skills/_shared/codex-config.md +0 -81
  35. package/config/skills/_shared/collect-codex-findings.py +0 -505
  36. package/config/skills/_shared/finish-gate.py +0 -521
  37. package/config/skills/_shared/grok-anchor-guard.py +0 -78
  38. package/config/skills/_shared/judge-output-parser.py +0 -200
  39. package/config/skills/_shared/judge-role-evidence.py +0 -320
  40. package/config/skills/_shared/phase-prompt-render.py +0 -183
  41. package/config/skills/_shared/process-evidence.py +0 -926
  42. package/config/skills/_shared/resolve-bootstrap.py +0 -1410
  43. package/config/skills/_shared/resolve-stop-hook.py +0 -359
  44. package/config/skills/_shared/run-bounded.py +0 -58
  45. package/config/skills/_shared/spec-verify-check.py +0 -5083
  46. package/config/skills/_shared/state-phase-write.py +0 -5989
  47. package/config/skills/_shared/terminal-claim-check.py +0 -777
  48. package/config/skills/_shared/verify-merge-findings.py +0 -3528
  49. package/config/skills/devlyn-design-ui/SKILL.md +0 -650
  50. package/config/skills/devlyn-ideate/references/from-spec-mode.md +0 -61
  51. package/config/skills/devlyn-ideate/references/project-mode.md +0 -80
  52. package/config/skills/devlyn-ideate/references/spec-template.md +0 -121
  53. package/config/skills/devlyn-ideate/references/templates/decision.md +0 -48
  54. package/config/skills/devlyn-ideate/references/templates/roadmap.md +0 -50
  55. package/config/skills/devlyn-ideate/references/templates/vision.md +0 -44
  56. package/config/skills/devlyn-queue/SKILL.md +0 -66
  57. package/config/skills/devlyn-queue/scripts/append.py +0 -94
  58. package/config/skills/devlyn-resolve/SKILL.md +0 -379
  59. package/config/skills/devlyn-resolve/references/free-form-mode.md +0 -71
  60. package/config/skills/devlyn-resolve/references/outer-loop.md +0 -23
  61. package/config/skills/devlyn-resolve/references/phases/build-gate.md +0 -75
  62. package/config/skills/devlyn-resolve/references/phases/cleanup.md +0 -33
  63. package/config/skills/devlyn-resolve/references/phases/implement.md +0 -44
  64. package/config/skills/devlyn-resolve/references/phases/plan.md +0 -39
  65. package/config/skills/devlyn-resolve/references/phases/probe-derive.md +0 -261
  66. package/config/skills/devlyn-resolve/references/phases/surface-close.md +0 -34
  67. package/config/skills/devlyn-resolve/references/phases/verify.md +0 -310
  68. package/config/skills/devlyn-resolve/references/state-schema.md +0 -152
  69. package/config/skills/devlyn-resolve/references/task-completion.md +0 -160
  70. package/config/templates/prompt-templates.md +0 -71
  71. package/config/templates/template-feature.spec.md +0 -255
  72. package/config/templates/template-product-spec.md +0 -680
@@ -1,379 +0,0 @@
1
- ---
2
- name: devlyn-resolve
3
- description: Default to direct work after inspecting scope and verification, including bounded multi-file changes. Use full resolve for explicit resolve/spec-mode requests, queue drains, or concrete interacting requirements or verification too complex for the current context. Domain labels, file count, spec documents and quoted skill paths alone do not trigger it. Investigate or clarify missing intent/access first. Explicit small resolve retains all phases and independent verification. Verify dual-judge is default-when-available.
4
- ---
5
-
6
- The current CLI owns PHASE 0, state transitions, Git checkpoints and final report/archive. Spawn the canonical phase workers from this process; never delegate the whole run to another parent orchestrator. VERIFY uses a fresh, findings-only worker.
7
-
8
- <pipeline_config>
9
- $ARGUMENTS
10
- </pipeline_config>
11
-
12
- <orchestrator_context>
13
- Long-horizon agentic work; context auto-compacts. State lives in `.devlyn/pipeline.state.json` — the single authoritative verdict source. Schemas in `references/state-schema.md`. Best at `xhigh` effort.
14
- </orchestrator_context>
15
-
16
- <autonomy_contract>
17
- Hands-free. Measured by how far we get without human intervention.
18
-
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` 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
- 3. Order: PLAN → RISK_PROBES? → IMPLEMENT → SURFACE_CLOSE? → BUILD_GATE → CLEANUP → VERIFY → FINAL_REPORT. No others.
22
- 4. Orchestrator does not write code. It parses input, spawns phases, reads state, branches on verdicts, emits the report.
23
- 5. Halt only on unrecoverable worker failure, empty IMPLEMENT, refused shared repair admission or VERIFY BLOCKED, or SURFACE_CLOSE timeout / input-or-prompt mismatch / attestation failure / execution-audit violation / out-of-surface delta / rollback failure; adjudication-malformed SURFACE_CLOSE continues after successful rollback plus transcript write-audit; otherwise continue.
24
- </autonomy_contract>
25
-
26
- <harness_principles>
27
- Every phase applies Subtractive-first / Goal-locked / No-workaround / Evidence, loaded by file or inline; Codex routes receive it inline.
28
- </harness_principles>
29
-
30
- <runtime_paths>
31
- Before PHASE 0 or any phase command, establish the bindings below.
32
-
33
- Resolve bundled resources from the SKILL.md loaded for this invocation.
34
-
35
- Reader-rendered directory hint:
36
- ```text
37
- ${CLAUDE_SKILL_DIR}
38
- ```
39
-
40
- Treat the hint as literal path data, never shell code. If the reader
41
- replaced it with an absolute directory, use that directory. Otherwise
42
- use the filesystem path or base directory reported for this loaded
43
- SKILL.md. Resolve virtual URIs through the reader's native filesystem
44
- mapping. If available source locations name different directories,
45
- stop.
46
-
47
- Bind DEVLYN_SKILL_DIR to that absolute directory. Do not obtain this
48
- binding from an environment variable, cwd, or another installation.
49
- Verify its SKILL.md before proceeding. Missing or conflicting source
50
- identity is BLOCKED:skill-source-unresolved; include the failed path
51
- when known.
52
-
53
- Resolve bundled references against this directory. These bindings are
54
- workflow values: establish them explicitly using each tool or shell's
55
- literal-path rules, and include their absolute values in every fresh
56
- worker's prompt, telling it to set them from those values, never from
57
- its inherited environment. Do not rely on shell state surviving between
58
- calls.
59
-
60
- Resolve directory symlinks on DEVLYN_SKILL_DIR before deriving its
61
- sibling _shared. Bind that directory as DEVLYN_SHARED_DIR. References
62
- written as _shared/... use this binding. Verify the directory and each
63
- required resource before use; failure is BLOCKED:shared-dir-unresolved
64
- with the failed path. Never search another installation.
65
-
66
- Bind CODEX_MONITORED_PATH to DEVLYN_SHARED_DIR/codex-monitored.sh and verify that file before proceeding. Pass DEVLYN_SKILL_DIR, DEVLYN_SHARED_DIR, and CODEX_MONITORED_PATH explicitly to every fresh phase worker. In omp, when this skill was selected by name, resolve it with `printf '%s\n' skill://devlyn-resolve` in its Bash tool.
67
- </runtime_paths>
68
-
69
- <engine_routing>
70
- Each model-invoked 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.
71
-
72
- - PLAN runs in the owner context; BUILD_GATE and CLEANUP run orchestrator commands with no separate model. The selected IMPLEMENT worker also owns code/doc cleanup before its final checkpoint. IMPLEMENT and VERIFY still require fresh workers. If the current CLI cannot spawn a required 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.
73
- - Claude Code model-invoked phases: spawn `Agent` (`mode: "bypassPermissions"`); prompt = adapter-header + canonical-body + task-context.
74
- - Codex CLI model-invoked phases: shell out via `bash "$CODEX_MONITORED_PATH"` with the same compounded prompt. Each `codex exec` child is a new session/fresh context. Write the compounded prompt to a file and set `DEVLYN_CODEX_PROMPT_FILE` with sole prompt argument `-`; the wrapper snapshots exact stdin bytes, seals transport evidence and emits a heartbeat. Without file transport stdin remains DEVNULL. No MCP. The wrapper call is foreground-blocking — never a background shell (`run_in_background`, `&`, `nohup`), never end your message while it runs: headless print-mode wind-down kills backgrounded children (0-byte delivery); the heartbeat is the observability channel.
75
- - oh-my-pi model-invoked 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.
76
- - Default engine: Claude when the orchestrator has Claude Code’s native `Agent`; otherwise its own fresh worker. PLAN is orchestrator-fixed and never inherits `--engine`, an executor pin, or `state.engine`. BUILD_GATE runs orchestrator commands; probes retain their existing routes. `_shared/engine-preflight.md#role-resolution` defines the single resolver and explicit `--role-config` / project role precedence. Absent profiles preserve legacy `--engine` / executor / default behavior; VERIFY primary may be independently selected. Configured role/priority pins and flags fail closed on unavailable engine or failed authentication, including failures discovered at dispatch. Unconfigured automatic VERIFY selects an available OTHER engine.
77
- - The `--engine` flag does not disable default pairing: the second judge uses the OTHER engine by default when available.
78
- - Multi-LLM evolution: when a new model adapter ships in `_shared/adapters/`, that engine becomes selectable for the roles its adapter declares eligible without further skill changes (NORTH-STAR.md "Multi-LLM evolution direction").
79
- </engine_routing>
80
-
81
- <modes>
82
- Three input shapes:
83
-
84
- 1. **Free-form**: `/devlyn-resolve "fix the login bug"` (inline goal) or `/devlyn-resolve --goal-file <path>` (goal text read from a file — the devlynd `ResolveAdapter` launcher path, injection-safe). PHASE 0 runs the complexity classifier and either proceeds with an internal mini-spec (trivial), drafts focused questions for in-prompt resolution (medium), or synthesizes a best-effort spec with a logged `## Assumptions` block (large; zero-scope-signal goals halt). No mid-pipeline prompts in any branch.
85
- 2. **Spec**: `/devlyn-resolve --spec docs/roadmap/phase-N/X.md`. Supplied specs and expected files are read-only inside resolve. Stage verification commands from sibling `spec.expected.json`; if absent, use the legacy `## Verification` JSON block.
86
- 3. **Verify-only**: `/devlyn-resolve --verify-only <diff-or-PR-ref> --spec <path>`. Skips PHASE 1-4. Runs PHASE 5 (VERIFY) on the supplied diff against the spec.
87
- </modes>
88
-
89
- <post_implement_invariant>
90
- After `state.implement_passed_sha` is set: SURFACE_CLOSE and CLEANUP are limited to their allowlists (empty SURFACE_CLOSE PASS is valid; violations revert); VERIFY is fresh-context, findings-only, and mutation-free. No fresh VERIFY worker → `BLOCKED:fresh-context-unavailable`, never same-context review.
91
- </post_implement_invariant>
92
-
93
- <transition_protocol>
94
- For every direct complete→spawn handoff, call `state-phase-write.py ... --phase <current> transition` with the current phase's normal completion/attestation arguments plus caller-specified `--next-phase`, `--next-round`, `--next-triggered-by`, `--next-engine`, `--next-model`, and any next-phase metadata. For owner PLAN/BUILD_GATE/CLEANUP, omit engine/model/prompt/session arguments. PLAN records `execution_kind: "orchestrator_context"`; BUILD_GATE/CLEANUP record `execution_kind: "orchestrator_commands"`, all with null worker identity. Existing metadata-bearing PLAN/CLEANUP and already-open absent-kind BUILD spans retain historical worker validation; never relabel their evidence. The verb validates a legal edge and commits both lifecycle writes atomically; it returns JSON state facts only. It never selects a phase/engine, renders a prompt, or spawns an agent. Use standalone `spawn` only for the initial post-bootstrap dispatch; use standalone `complete` when no next phase opens (including a halt).
95
- </transition_protocol>
96
-
97
- ## PHASE 0: PARSE + CLASSIFY + ROUTE
98
-
99
- Outer-owner boundary: before normal task writes, follow `references/task-completion.md` for prospective task-branch ownership (own linked worktree) and `references/outer-loop.md` for owner-input commits. Existing branches cannot be retroactively adopted. Verify-only does not allocate or publish; phase workers never own delivery.
100
-
101
- 1. Run the bootstrap once with the exact tokenized `<pipeline_config>` and this orchestrator's default engine from `<engine_routing>`:
102
-
103
- ```bash
104
- DEVLYN_DEFAULT_ENGINE="<current-cli-default>" python3 "$DEVLYN_SHARED_DIR/resolve-bootstrap.py" <pipeline_config tokens>
105
- ```
106
-
107
- Read its sole JSON result. On `ok:false`, halt on its exact report-level `blocked` string and show `detail`; init failures create no phase verdict; crashes can leave owned residue, which bootstrap preserves and refuses to replace. Admission refusals require continuing the existing run through its owning session or starting in a distinct worktree. Never autoarchive to defeat a refusal; explicit manual archive is recovery only after the operator establishes that prior writers stopped. Valid completed runs still autoarchive normally, but a completed final report does not prove every interactive writer exited. On success, the script has atomically initialized the schema-v3 skeleton (`pair_verify: true` only when `--pair-verify` was passed), stamped the null-safe Claude session id, persisted exact-byte Goal/spec identity, staged spec verification inputs, and captured the verify-only external diff. It validates only flags needed for those init fields, including mode exclusivity, `--max-rounds`, `--bypass`, and `--pair-verify`/`--no-pair`; `--pair-verify` and `--no-pair` are mutually exclusive. Free-form init sets `state.source.type = "generated"`. Spec staging validates supported `complexity` frontmatter (including sibling spec `complexity` frontmatter). It stages and validates an explicit `--role-config` object into state with its path/digest, but does not resolve roles, write the untracked baseline, classify complexity/risk or announce. `state.engine` is the raw `--engine` value with `engine_source: "flag"`, otherwise the passed `DEVLYN_DEFAULT_ENGINE` with `engine_source: "default"`; step 2 resolves and replaces both fields.
108
-
109
- 2. Engine pre-flight: follow `_shared/engine-preflight.md`. Freeze the shared resolver once before any phase with `python3 "$DEVLYN_SHARED_DIR/state-phase-write.py" --devlyn-dir .devlyn --freeze-roles --default-engine "<current-cli-default>"`. It validates project/per-run roles and records `state.role_resolution`; legacy `state.engine`/`engine_source` remain the executor. Repeated reads return the same snapshot, never changed project settings. Keep availability/auth checks before each selected dispatch; explicit unavailable routes fail closed. These PHASE0 failures are report-level `BLOCKED:<reason>`; phase verdict carriers remain bare enums.
110
-
111
- 3. Write `.devlyn/untracked.baseline`: `python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --write-untracked-baseline`.
112
-
113
- 4. Read the Goal/spec through `state.source`. For free-form mode, run the deterministic classifier in `references/free-form-mode.md`. Zero-scope-signal goals halt with `BLOCKED:large-needs-ideation`. Follow the selected branch to write the trivial/medium/large body of `.devlyn/criteria.generated.md`, then set `state.complexity` and its raw-byte `criteria_sha256`. The Large `## Assumptions`/recommendation/final-report obligations remain unchanged.
114
-
115
- Compute `state.risk_profile` from the user goal plus spec/criteria text. Mark `high_risk: true` for 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 both probe booleans true. Otherwise an automatic high-risk route enables probes only when the legacy executor’s OTHER engine (legacy pair priority/complement, independent of VERIFY profiles) is available and `--no-risk-probes` is absent; if unavailable, keep probes disabled and append `auto-risk-probes skipped: <engine>-unavailable`. `--no-pair` sets `pair_default_enabled: false`. Preserve strict boolean/list types and concise string reasons.
116
-
117
- 5. Announce one line: `resolve starting — run <run_id> — engine <engine> — mode <mode> — complexity <complexity-or-na> — pair <on|solo:auto_pair_other_engine_unavailable|disabled> — risk_probes <on|off>`.
118
-
119
- 6. Open the initial post-bootstrap span with standalone `state-phase-write.py ... spawn`: `plan` before owner planning (normal run), or `verify` before MECHANICAL (verify-only). The bootstrap never chooses a phase, engine, branch, prompt, or agent.
120
-
121
- ### Explicit role dispatch
122
-
123
- Use the frozen role entry and `role-config.py --state .devlyn/pipeline.state.json --role <worker|primary_judge|pair_judge> [--resolved-model <exact inherited model>]` before each affected dispatch. Keep model/effort values as individual argv elements. Replace that role's model/effort options, never append duplicates; omitted fields retain its current phase options. PLAN, BUILD_GATE, probes and SURFACE_CLOSE do not inherit worker/VERIFY profiles. A native option warning that ignores/rejects a requested field is BLOCKED even at exit0. Print requested values, source and channel before dispatch; report observed values only from validated evidence.
124
-
125
- For a Codex worker with explicit effort, retain the exact wrapper arguments (excluding `bash` and the wrapper path) as `.devlyn/<phase>.argv.<round>.json` before launch. The wrapper's existing invocation receipt binds that array's canonical JSON hash; completion checks the requested effort against it. This adds no mutation receipt relaxation or new launcher.
126
-
127
- For a judge with explicit model/effort, let `JUDGE_STEM=.devlyn/<engine>-judge.r<VERIFY-round>`. Retain the exact UTF-8 prompt as `$JUDGE_STEM.prompt` and complete dispatched argv array (including launcher) as `$JUDGE_STEM.argv.json`. Codex sets `DEVLYN_CODEX_PROMPT_FILE="$JUDGE_STEM.prompt"` and passes the sole prompt `-`; Claude uses `run-bounded.py 600 --stdin-file "$JUDGE_STEM.prompt" --record-transport -- claude -p` with no positional prompt. Preserve the generated `$JUDGE_STEM.prompt.transport.json`; it binds the snapshotted bytes and actual native argv, including wrapper-added isolation. Missing/mismatched transport evidence BLOCKs authentication; never fabricate the carrier. Codex uses the isolated read-only monitored route with timeout600 and redirects stdout/stderr to `$JUDGE_STEM.stdout`/`$JUDGE_STEM.stderr`. Claude uses the existing 600s `run-bounded.py` CLI route, `--tools Read,Grep,Glob --allowedTools Read,Grep,Glob --permission-mode dontAsk --setting-sources project --strict-mcp-config --mcp-config '{"mcpServers":{}}' --output-format json`; redirect raw JSON to `$JUDGE_STEM.output.json` and stderr to `$JUDGE_STEM.stderr`. Never substitute a Claude CLI worker for native Agent.
128
-
129
- After the actual process exits, run `python3 "$DEVLYN_SHARED_DIR/judge-role-evidence.py" --devlyn-dir .devlyn --role <primary_judge|pair_judge> --exit-code <actual-exit>`. On failure preserve raw artifacts and BLOCK the source; never create successful evidence manually. On success it retains round-scoped evidence and produces canonical `.devlyn/<engine>-judge.stdout` (exact Claude result extraction, exact Codex stdout copy) for the unchanged findings collector. Merge revalidates/binds evidence and archives all bound originals, including prior rounds. Missing explicit judge evidence cannot become PASS. Unconfigured judge routes retain their existing capture format.
130
-
131
- ## PHASE 1: PLAN
132
-
133
- Skip in verify-only mode. PLAN is orchestrator-fixed and never inherits `--engine`, an executor pin, or `state.engine`. The owner reads `references/phases/plan.md` and the original request/spec, then writes `.devlyn/plan.md` before product edits. Open with `state-phase-write.py ... --phase plan spawn --round 0`, without worker identity or a prompt dispatch. This is owner reasoning, not zero-cost mechanical work.
134
-
135
- Keep the file list/authorized surface, risks and verbatim verification requirements. Large work may include the existing execution-phase section only when every boundary has a runnable gate. Completing PLAN binds the exact output digest; all later mutations rehash it. Never widen it after implementation starts.
136
-
137
- After return:
138
- 1. If `.devlyn/plan.md` lists zero files → halt with verdict `BLOCKED:plan-empty`.
139
- 2. If the plan exceeds the authorized scope, the sole correction is owner round 1 with `--triggered-by plan`, before implementation starts. Narrow to the original authorization, complete and bind the new output; a second failed plan halts. No worker dispatch or new permission is implied.
140
- 3. After any re-spawn above, if `state.risk_profile.risk_probes_enabled == true` and `state.risk_profile.risk_probes_explicit == false`, parse the `authorized_surface` array from the JSON block under `<!-- devlyn:authorized-surface -->`. For a well-formed string array, compute `probe_scale_small := len(authorized_surface) <= 2 AND no entry ends in "/**"`. If true, set `risk_probes_enabled = false`, leave `high_risk` unchanged, and append `auto-risk-probes demoted: plan surface small (<n> paths)` to `reasons` using the actual length. A missing or malformed block leaves probe state unchanged; BUILD_GATE owns its malformed-block failure.
141
-
142
- ## PHASE 1.5: RISK_PROBES
143
-
144
- Skip unless `--risk-probes` is set OR `state.risk_profile.risk_probes_enabled`
145
- is true. This phase is findings-as-executable-checks, not a second plan and not
146
- debate. When it runs, the OTHER engine is required: if unavailable, halt with
147
- `BLOCKED:<engine>-unavailable` plus setup guidance;
148
- do not silently continue without probes. Reaching this halt means the route was
149
- explicitly requested (`--risk-probes`) — an auto high-risk escalation toward an
150
- unavailable OTHER engine was already gated off in PHASE 0 by leaving
151
- `risk_probes_enabled: false`, so a single-engine high-risk run proceeds solo here.
152
-
153
- Engine: OTHER engine from the legacy executor and legacy pair priority/complement; worker/VERIFY profiles do not reroute probes. Prompt body:
154
- `references/phases/probe-derive.md`.
155
-
156
- Inputs: source spec/criteria, `.devlyn/plan.md`, and repo read/search. Forbidden:
157
- `spec.expected.json`, `.devlyn/spec-verify.json`, `BENCH_FIXTURE_DIR`, hidden
158
- fixture/verifier paths, previous findings, and harness docs unless excerpted.
159
-
160
- Output: `.devlyn/risk-probes.jsonl`, 1 to 3 JSONL entries. Each entry must be
161
- one verification command shape plus `id`, `derived_from`, `tags`, and
162
- `tag_evidence`, where `derived_from` is an exact substring of the visible
163
- `## Verification` bullet the command directly exercises. `tag_evidence` must be
164
- a JSON object keyed by tag, with marker arrays as values; a top-level array or
165
- tag-only probe is malformed. `ordering_inversion` must include
166
- `input_order_would_choose_wrong_winner` and `asserts_processing_order_result`;
167
- `prior_consumption` must include `same_resource_consumed_first` and
168
- `later_entity_fails_or_reroutes`; `stdout_stderr_contract` must include
169
- `asserts_named_stream_output`; `error_contract` must include
170
- `asserts_error_payload_or_stderr` and `asserts_nonzero_or_exit_2`.
171
- `http_error_contract` must include `asserts_http_error_status` and
172
- `asserts_error_payload_body`.
173
- `auth_signature_contract` must include `asserts_signature_over_exact_bytes` and
174
- `asserts_tampered_or_missing_signature_rejected`; `idempotency_replay` must
175
- include `first_delivery_then_duplicate` and
176
- `duplicate_id_rejected_regardless_of_body`; `concurrent_state_consistency` must
177
- include `overlapping_mutations_exercised`,
178
- `all_successful_responses_reflected`, and `distinct_identifiers_asserted`;
179
- `atomic_batch_state` must include `mixed_valid_invalid_batch`,
180
- `asserts_store_unchanged_after_failure`, and
181
- `asserts_success_order_and_distinct_ids`.
182
- When visible text names exact keys, fields, row shapes, JSON objects, response
183
- bodies, stdout/stderr objects, or exact error bodies, `shape_contract` must
184
- include `uses_visible_input_key_names`, `asserts_visible_output_key_names`, and
185
- `asserts_no_unexpected_output_keys`; exact JSON error objects/bodies must also
186
- include `visible_text_names_exact_json_error_object` and
187
- `asserts_exact_error_object`. Cart/pricing success probes should use
188
- `shape_contract` unless they satisfy the `ordering_inversion` markers. The probe
189
- command must not reference external network URLs; use only worktree-local or
190
- localhost resources.
191
- For high-complexity specs with multiple behavior bullets, at least one probe
192
- must be compound: it must exercise two or more visible verification bullets in a
193
- single command. Empty output is invalid when `--risk-probes` is set.
194
-
195
- State write: `phases.probe_derive.{started_at, verdict, completed_at, duration_ms, artifacts}`.
196
-
197
- Invocation contract when OTHER engine is Codex:
198
-
199
- - Invoke Codex only through the monitored wrapper path in `CODEX_MONITORED_PATH`
200
- resolved from `DEVLYN_SHARED_DIR`:
201
- `DEVLYN_CODEX_PROMPT_FILE="<probe-prompt-file>" CODEX_MONITORED_ISOLATED=1 bash "$CODEX_MONITORED_PATH" -C "$PWD" -s workspace-write -c sandbox_workspace_write.network_access=false -c model_reasoning_effort=high -`.
202
- Append `-c sandbox_workspace_write.network_access=true` only when a probe's visible Verification command requires a localhost service (for example, a DB test harness); never as a default.
203
- Isolation keeps user config, AGENTS.md, hooks, and project rules
204
- from adding hidden context, tool calls, or transcript side effects.
205
- - Do not run `codex`, `codex exec`, `/Users/.../codex`, or a plugin-provided
206
- Codex binary directly. A raw Codex child can outlive the phase and makes the
207
- run invalid even if `.devlyn/risk-probes.jsonl` is written.
208
- - Capture wrapper stdout/stderr to `.devlyn/probe-derive.stdout` and
209
- `.devlyn/probe-derive.stderr`; branch on the wrapper exit code before
210
- validating `.devlyn/risk-probes.jsonl`.
211
-
212
- After return:
213
- 1. Run `python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --validate-risk-probes`
214
- for the artifact boundary before IMPLEMENT; malformed probes halt with
215
- `BLOCKED:probe-derive-malformed`.
216
- 2. Compute `python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --print-risk-probes-digest` and write the result to top-level `state.risk_probes_digest`:
217
- ```bash
218
- RISK_PROBES_DIGEST="$(python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --print-risk-probes-digest)"
219
- python3 - "$RISK_PROBES_DIGEST" <<'PY'
220
- import json, pathlib, sys
221
- path = pathlib.Path(".devlyn/pipeline.state.json")
222
- state = json.loads(path.read_text())
223
- state["risk_probes_digest"] = sys.argv[1]
224
- path.write_text(json.dumps(state, indent=2) + "\n")
225
- PY
226
- ```
227
- Any later legitimate probe regeneration is orchestrator-only and repeats validate plus digest-write.
228
- 3. IMPLEMENT receives `.devlyn/plan.md` plus `.devlyn/risk-probes.jsonl` as
229
- concrete acceptance obligations. It must not receive the producer engine's
230
- commentary or any mention of pair/critic/debate.
231
-
232
- ## PHASE 2: IMPLEMENT
233
-
234
- Skip in verify-only mode. Constrained design judgment within PLAN's invariants. Writes code, tests, and inline doc-comments. No standalone DOCS phase — what the spec licenses is updated here, what it does not is out of scope.
235
-
236
- Engine/model/effort: frozen `role_resolution.roles.worker`, including code/doc cleanup within this same invocation before its final checks; obtain validated argv additions using `role-config.py --state .devlyn/pipeline.state.json --role worker --resolved-model <existing-exact-phase-model>`. Prompt body: `references/phases/implement.md`.
237
-
238
- For every Codex-routed IMPLEMENT spawn, render the exact
239
- prompt to `.devlyn/<phase>.prompt.<round>`, pass its SHA-256 to `state-phase-write.py
240
- spawn --prompt-sha256`, and invoke only through `codex-monitored.sh` with these
241
- seven variables set to the active state identity:
242
- `DEVLYN_INVOCATION_{RUN_ID,PHASE,ROUND,WORKDIR,PROMPT_FILE,SESSION_FILE,RECEIPT}`.
243
- Set `DEVLYN_CODEX_PROMPT_FILE` to that same prompt file and pass sole prompt `-`; retain its generated `.transport.json` carrier.
244
- The session and receipt paths are `.devlyn/<phase>.worker-session.<round>.jsonl`
245
- and `.devlyn/<phase>.invocation.<round>.json`. These three paths are round-scoped
246
- so a retry cannot overwrite earlier prompt/session evidence. Every invocation
247
- must include `--json -m <model_requested>` and `-c sandbox_workspace_write.network_access=<true|false>`: exactly
248
- `false` for IMPLEMENT, so
249
- user configuration cannot silently change the phase capability. Redirect wrapper stdout directly
250
- to that session path. The wrapper rejects bypass/yolo flags and any sandbox other
251
- than `workspace-write`. The receipt seals the requested model, sandbox, phase-scoped
252
- network capability, prompt, terminal exit, and session digest; completion passes the
253
- same canonical session via `--engine-session-log`. A missing/mismatched receipt
254
- or same-round retry blocks. Respawn with a new round instead of replacing it.
255
- Historical metadata-bearing PLAN/CLEANUP and absent-kind BUILD spans retain their original receipt-bound worker routes (network false for PLAN/CLEANUP, true for BUILD). New owner spans never create worker receipts.
256
-
257
- State write: `phases.implement.{started_at, verdict, completed_at, duration_ms}`.
258
-
259
- Before accepting an IMPLEMENT `PASS` / `PASS_WITH_ISSUES` completion or
260
- transition, `state-phase-write.py` validates every sibling
261
- `spec.expected.json.process_evidence[]` obligation for `phase: "implement"`,
262
- rehashes its raw streams, and appends the validated carrier to
263
- `state.process_evidence`. A missing, altered, escaping, duplicate, or
264
- expectation-mismatched carrier blocks the checkpoint.
265
-
266
- **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:
267
- 1. `git diff --stat` — empty diff → halt with `BLOCKED:implement-empty`.
268
- 2. Checkpoint (**scoped staging** — this exact shape everywhere a pipeline commit is made): `bash -o pipefail -c 'python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --print-authorized-surface | git add --pathspec-from-file=- --pathspec-file-nul' && git commit -m "chore(pipeline): implement"`.
269
- 3. After the checkpoint succeeds, set `state.implement_passed_sha = git rev-parse HEAD` before downstream phases (activates `<post_implement_invariant>`).
270
-
271
- **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:
272
- 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.
273
- 2. After return: run the phase's `gate:` commands directly — deterministic, exit-code truth, no LLM judgment.
274
- 3. Gate PASS → scoped-staging checkpoint with message `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).
275
- 4. Gate FAIL → no commit. Persist `exec.statuses[k-1] = "FAIL"`, retain `exec.current`, and request IMPLEMENT admission with a fresh invocation round and null trigger. The writer charges the shared repair budget on admission. On budget refusal, close the still-open IMPLEMENT span with `--verdict FAIL`, then report `BLOCKED:repair-budget-exhausted` with the gate output, origin and counters.
276
-
277
- 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).
278
-
279
- ## PHASE 2.5: SURFACE_CLOSE
280
-
281
- Run once iff `state.source.type == "generated"` and complexity is trivial/medium. Engine is Claude always, pair-judge-routed; executor flag/pin ignored. Per `_shared/engine-preflight.md`, unavailability skips via `surface-skip`, reports `auto_surface_close_claude_unavailable`, never BLOCKs/reroutes.
282
-
283
- Freeze `.devlyn/surface-close.input.patch`; assemble Claude adapter + `references/phases/surface-close.md` canonical body VERBATIM + data-only Goal/patch/hashes/surface/commands; write the exact bytes to `.devlyn/surface-close.prompt.<round>`, hash them and spawn with `--tools "Read,Grep,Glob,Edit,Write" --dangerously-skip-permissions --output-format json --strict-mcp-config --mcp-config '{"mcpServers":{}}'`, omitting `--model` at SPW spawn to inherit native Claude model settings. Record the effective model from the native JSON result. Use no positional prompt on the CLI route and retain its generated transport carrier. Bound at 600s (native or `run-bounded.py 600 --stdin-file .devlyn/surface-close.prompt.<round> --record-transport -- claude -p`), else block pre-spawn. Save raw stdout as `.devlyn/surface-close.output.json`, extract its `result` string to `.devlyn/surface-close.stdout`, retain `.devlyn/surface-close.worker-session.<round>.jsonl`, and complete with `.devlyn/surface-close.output.json` as `--engine-session-log`; non-JSON/missing result follows the existing failure path. Workers execute nothing; `surface-check` gates rows, citations, scope, and execution. Empty PASS completes; authorized delta is scoped-staged and committed. Only `BLOCKED:surface-close-adjudication-malformed` runs `surface-adjudication-recover --authorized-surface-json <array>`, whose successful rollback and transcript Edit/Write audit complete the nonterminal skip carrier and continue to BUILD_GATE. Timeout, input/prompt mismatch, attestation failure, execution-audit violation, and out-of-surface delta run `surface-rollback`, complete bare `BLOCKED`, and halt; rollback failure also halts. One shot; no `max_rounds`.
284
-
285
- **Common post-fix checkpoint (BUILD_GATE and VERIFY):** CLEANUP findings use the same checkpoint with origin `cleanup`. After the budget-admitted fix IMPLEMENT returns, scoped-stage the authorized surface and commit `chore(pipeline): implement fix round <n>`, where `<n>` is the IMPLEMENT invocation round; run `python3 "$DEVLYN_SHARED_DIR/state-phase-write.py" --devlyn-dir .devlyn --phase implement durability-enforce --round <n> --origin-phase <build_gate|cleanup|verify>`. Its receipt must PASS before re-entry, which enforces it again before VERIFY artifact clearing or phase spawn.
286
-
287
- ## PHASE 3: BUILD_GATE
288
-
289
- Skip in verify-only mode OR when `build-gate` in `state.bypasses`. Deterministic — same commands CI / Docker / production run.
290
-
291
- The orchestrator reads `references/phases/build-gate.md` and runs the gates directly: no adapter header, rendered worker prompt or separate model. Open its span through the predecessor transition with no BUILD engine/model/prompt/session arguments. New spans record `execution_kind: "orchestrator_commands"`; historical already-open spans with that field absent retain their worker attestation contract.
292
-
293
- Follow the BUILD body's capability contract on the parent's actual command route and log that basis; no child receipt attests it. Authoritative denial yields report-level `BLOCKED:build-env-underprovisioned` (bare `BLOCKED` in state). Keep spec/probe capture and its conditional post-spec denial carrier refresh; ordinary gates retain findings and raw logs. The orchestrator:
294
- 1. Detects language/framework via project files (`package.json`, `pyproject.toml`, etc.).
295
- 2. Runs language-specific gates (tsc / lint / test), deferring exact literal overlaps under `references/phases/build-gate.md`'s current-contract/current-round rule; task-context prompts must preserve that rule.
296
- 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. The script routes each command through `process-evidence.py`; preserve `.devlyn/spec-verify.results.json` and its validated BUILD_GATE carrier for the state-bound archive flow.
297
- 4. If diff touches web-surface files: run the browser tier with the repo's available toolchain (for example Playwright or curl).
298
- 5. Emits `.devlyn/build_gate.findings.jsonl` + `.devlyn/build_gate.log.md`.
299
-
300
- State write: `phases.build_gate.{execution_kind, started_at, verdict, completed_at, duration_ms, artifacts}`; engine/model fields are null, with no worker prompt or receipt. Completion keeps PLAN integrity and process-evidence floors.
301
-
302
- Branch:
303
- - `PASS` → PHASE 4.
304
- - `FAIL` → before product repair, halt with report-level `BLOCKED:required-tools-unavailable` only if a parent probe confirms the failed invocation's external gate tool is absent, unchanged `state.base_ref.sha` configuration requires it, and cited explicit task/spec constraints prohibit supplying it through any authorized route. Derive the tool from that configuration and invocation, never diagnostic text; bind the probe's interpreter, work root and normalized child environment to the runner call sites and actual gate invocation, not parent PATH or a manifest command string alone. An available authorized tool route, product-module/configuration failure, changed tool configuration, permitted or unspecified supply, or uncertain probe/identity/environment retains the fix loop. A confirmed prohibited-tool blocker wins even with other product findings: leave them unrepaired and preserve phase `FAIL`, findings, raw streams and sealed manifests unchanged. Record the probe and constraint citation separately in parent halt evidence; no repair means no round increment. Otherwise request BUILD_GATE-origin IMPLEMENT admission with `--next-triggered-by build_gate` and the findings, then run the common post-fix checkpoint. On `BLOCKED:repair-budget-exhausted`, close an open BUILD_GATE with the original FAIL completion arguments, then report that terminal verdict with origin and counters.
305
- - `BLOCKED:build-env-underprovisioned` → halt immediately with the denied operation, original command, and process-evidence manifest/id; do not enter the product fix loop.
306
-
307
- ## PHASE 4: CLEANUP
308
-
309
- Skip if `cleanup` in `state.bypasses`. The owner reads `references/phases/cleanup.md`; no separate model invocation. Code/doc cleanup already belongs to the selected IMPLEMENT worker, with its original engine/model/effort and receipt.
310
-
311
- Open through the predecessor transition with `--next-pre-sha "$(git rev-parse HEAD)"`, omitting worker identity. Remove only observed generated, untracked/ignored artifacts owned by this run and not required deliverables; preserve source, user data, evidence and restart records. For PASS, tracked source and HEAD must equal `pre_sha`; the state writer checks them against `--post-sha` and rejects new untracked files relative to `.devlyn/untracked.baseline` (`.devlyn/` exempt). FAIL/BLOCKED closure preserves the observed delta and cannot enter VERIFY.
312
-
313
- If a new code/doc cleanup issue appears after BUILD, emit a concrete finding and request budget-admitted IMPLEMENT repair from CLEANUP FAIL with `--next-triggered-by cleanup`. On `BLOCKED:repair-budget-exhausted`, close an open CLEANUP with the original FAIL completion arguments and report `BLOCKED:repair-budget-exhausted` with origin and counters. After admission and repair, checkpoint and rerun BUILD_GATE before CLEANUP and fresh VERIFY; never reuse the earlier green checks. Keep the existing authorized surface, allowlist and durability checks. Do not broaden scope for cleanup.
314
-
315
- On success transition CLEANUP→VERIFY with `--post-sha "$(git rev-parse HEAD)"` and the next judge identity. VERIFY MECHANICAL runs independently on the final source.
316
-
317
- ## PHASE 5: VERIFY (fresh subagent, findings-only)
318
-
319
- Independent quality layer. MECHANICAL runs first on the post-CLEANUP code. Each
320
- JUDGE is then **spawned with empty conversation context** — no carry-over from
321
- PHASE 1-4 — and receives only `spec.md` (or
322
- `.devlyn/criteria.generated.md`), sibling `spec.expected.json`, the cumulative
323
- diff, the source hash, `.devlyn/spec-verify.results.json`, and the validated
324
- VERIFY process-evidence manifest/raw streams named by that results carrier. The
325
- fresh-context spawn plus immutable evidence inputs are the structural guarantee
326
- of independence.
327
-
328
- Before MECHANICAL, open VERIFY with frozen `role_resolution.roles.primary_judge`; without a profile it follows the legacy executor. Use that engine/model and the current round/trigger metadata in the predecessor's `<transition_protocol>` handoff, including bypass and repair paths; initial verify-only uses PHASE 0's standalone spawn. This records the phase span, not a judge invocation, and requires no rendered judge prompt or prompt digest.
329
-
330
- Two sub-phases:
331
-
332
- 1. **MECHANICAL** (deterministic, orchestrator-owned): 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` plus `.devlyn/spec-verify.results.json` with the VERIFY process-evidence carrier. Validate that carrier and all named raw streams before JUDGE spawn; a verdict-binding blocker skips both judges.
333
-
334
- 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 sealed result 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. For high-complexity specs, review at least one interaction counterexample that combines ordering/priority with failure handling and state mutation against the implementation and sealed MECHANICAL results. JUDGE never executes literal verification, lint, test, build, risk-probe, or newly invented interaction commands; missing executable coverage is a finding against the expected contract/risk probes. After MECHANICAL passes, `--no-pair` continues solo with `user_no_pair`. Otherwise use the frozen pair profile/priority resolved against the primary. An unavailable OTHER engine or failed authentication blocks `--pair-verify` and configured pair role/priority pins; only an unconfigured automatic route records `auto_pair_other_engine_unavailable` and continues solo.
335
-
336
- When the OTHER engine is available, persist `pair_trigger` before spawn with `pair.default` plus every applicable outcome-independent reason, then dispatch both judges concurrently against the same frozen diff and sealed MECHANICAL evidence via foreground parallel dispatch, never background shells. A primary blocker never cancels the pair judge. If foreground parallel dispatch is unavailable, run the same two required judges sequentially; this changes dispatch shape only and never permits outcome-dependent escalation or a pair skip.
337
-
338
- Keep the existing reasons as telemetry: `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`. Append the outcome-dependent reasons only after both judges return. Eligible schema-v3 state must include `pair.default`; all other applicable reasons remain completeness-checked. Malformed risk or trigger state BLOCKs VERIFY.
339
-
340
- 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 reviews the two highest-risk explicit `## Verification` bullets that cross state mutation, all-or-nothing rollback, ordering, idempotency, auth, or error-priority clauses. 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 sealed MECHANICAL evidence. It performs at most two targeted reviews before first output; inspection remains read-only. When the spec names exact keys, row shapes, JSON object shape, or an exact error body, pair-JUDGE compares the sealed parsed key sets/deep equality so aliased keys, missing keys, and extra keys are verdict-binding failures. For priority/stateful specs, trace code order for 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. 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 review comes first: trace whether the earlier lower-priority entity loses because the later higher-priority entity is processed first, whether a failed/blocked middle entity leaves later state intact, and whether the sealed evidence covers complete accepted/scheduled and rejected output ordering. Missing executable coverage is a verdict-binding coverage finding; JUDGE does not invent or run the missing scenario. Pair-JUDGE output: emit JSONL findings then a bare terminal verdict line, or emit only `PASS` when clean. `_shared/judge-output-parser.py` is the single acceptance rule for pair output: JSONL findings, then a `# SUMMARY {json}` line or a bare verdict line (`PASS` alone when clean); it ignores bare code-fence lines and unwraps the registered Codex JSON envelope including its narrated-preamble recovery (iter-0082), and binds a whole-message NDJSON capture only through its uniquely attested terminal `end_turn` assistant message (iter-0106); every other non-empty line blocks the pair source. Both judgments merge with the rule "any HIGH/CRITICAL finding is verdict-binding; any MEDIUM with literal `verdict_binding: true` is also binding, regardless of confidence." Demonstrated applicable mandatory-clause violations, including unmet new requirements and incorrect customer documentation, must use one of those binding forms. Apply the canonical VERIFY clause/evidence and advisory boundaries; impact, rarity or pre-existing origin alone does not excuse an applicable violation. Cross-model disagreement on advisory lower-severity findings is logged but does not change the verdict. A primary JUDGE verdict-binding finding on a concurrent run does not cancel or discard the pair-JUDGE; both finding sets join the same fix round, and the merged verdict remains the worst source verdict without vote counting. `primary_judge_blocker` is parser-recognized only for archived v2.0 replay; new runs never write it. A verdict-binding MECHANICAL blocker still skips both judges and records `pair_judge: null`.
341
-
342
- If the OTHER engine is unavailable, only unconfigured automatic VERIFY records
343
- `auto_pair_other_engine_unavailable` and continues solo. Explicit `--pair-verify`,
344
- pair profiles and priority pins instead set VERIFY to `BLOCKED:<engine>-unavailable`, preserves the failed
345
- check, and prints setup guidance: install/configure the missing CLI, complete
346
- its auth/login flow, verify `<engine> --version`, then rerun. `--no-pair` is only
347
- for an intentional solo run.
348
-
349
- For policy-denied Windows Codex judge reads, prepare the complete inline evidence packet from `_shared/codex-config.md#constrained-windows-judge-reads` for each fresh judge, explicitly instructing no tools. Missing inputs produce a verdict-binding BLOCKED finding; sandbox, isolation, model/effort and 600s bounds remain unchanged.
350
-
351
- Findings are written to `.devlyn/verify.findings.jsonl`. **Except on the supplied no-tools route, VERIFY agents may inspect authorized source, diff and sealed evidence using native read/search tools or non-mutating shell commands. They must not mutate files or execute verification, lint, test, build, probe or invented-scenario commands.** Capture the primary reply as `.devlyn/<primary-engine>-judge.stdout` and its stderr sibling; never use the generic `.devlyn/verify-judge.stdout`. A Codex primary-JUDGE uses the distinct route `DEVLYN_CODEX_PROMPT_FILE="<primary-prompt-file>" CODEX_MONITORED_ISOLATED=1 CODEX_MONITORED_TIMEOUT_SEC=600 bash "$CODEX_MONITORED_PATH" -C "$PWD" -s read-only -c model_reasoning_effort=high - >.devlyn/codex-judge.stdout 2>.devlyn/codex-judge.stderr`: omit `-m` only without an explicit model; an explicit profile supplies the validated model/effort additions below. Omit bypass flags and capture both streams directly without pipes. On primary exit 124, write `.devlyn/verify.primary.timeout.json` with exactly `{"engine": "<resolved-primary-engine>", "budget_seconds": 600}` before merge. A malformed marker BLOCKs; a valid marker preserves canonical primary findings and floors `judge` at `BLOCKED` even when output is empty, never `PASS`, a solo verdict, or pair-style `TIMEOUT`. Without the marker, existing required-primary failure behavior is unchanged. For Codex pair-JUDGE, set `DEVLYN_CODEX_PROMPT_FILE="<pair-prompt-file>"` with sole prompt `-` and keep the read-only `codex-monitored.sh` route with `CODEX_MONITORED_ISOLATED=1 CODEX_MONITORED_TIMEOUT_SEC=600` and `-c model_reasoning_effort=medium`, no `tail`/`head`/`grep` pipes, and direct stdout/stderr capture; every other resolved OTHER engine follows `_shared/adapters/<name>.md` `## Invocation`. Capture the pair reply as `.devlyn/<other-engine>-judge.stdout`, then run `python3 "$DEVLYN_SHARED_DIR/collect-codex-findings.py" --devlyn-dir "<abs repo>/.devlyn" --stdout-file <other-engine>-judge.stdout` before merge. Raw stdout is diagnostic-only. A non-zero collector exit must write no canonical `.devlyn/verify.pair.findings.jsonl`; set the pair source to `BLOCKED` for `verify.pair.emission-contract` and never merge as though unparsed stdout were only a diagnostic. VERIFY prompts remain bounded, read-only, and must not ask the judge to edit `.devlyn`. On pair-JUDGE subprocess exit 124, write `.devlyn/verify.pair.timeout.json` with `{"engine": "<resolved name>", "budget_seconds": 600}` before merge. With that marker, a capture the parser rejects solely because its message stream was truncated before the terminal result stays `TIMEOUT`; every other parser rejection stays `BLOCKED`. The orchestrator records `phases.verify.judge_durations_ms: {judge: <int>, pair_judge: <int|null>}` as it collects each judge result; durations never nest inside `sub_verdicts`. After primary and pair findings are written, run `python3 "$DEVLYN_SHARED_DIR/verify-merge-findings.py" --write-state`; it rehashes and binds the VERIFY process-evidence carrier before merging. Branch only on the merged `state.phases.verify.verdict`; absent a BLOCKED source, 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, judge_durations_ms: {judge, pair_judge}, sub_verdicts: {mechanical, judge, pair_judge?}, artifacts}`.
352
-
353
- Branch:
354
- - `PASS` → PHASE 6.
355
- - `PASS_WITH_ISSUES` (no verdict-binding findings) → PHASE 6 with banner.
356
- - `NEEDS_WORK` → in normal mode, request IMPLEMENT admission with `triggered_by: "verify"` and the merged findings. On admission, run the common post-fix checkpoint with origin `verify`; on budget refusal, close VERIFY and report terminal `NEEDS_WORK`.
357
- - `BLOCKED` → close VERIFY and proceed directly to PHASE 6 with its blocker evidence; do not spawn IMPLEMENT or consume repair budget.
358
-
359
- ## PHASE 6: FINAL REPORT + ARCHIVE
360
-
361
- Open the `final_report` span through the predecessor's `state-phase-write.py --devlyn-dir .devlyn --phase <predecessor> transition --next-phase final_report --next-round 0 …` on a direct handoff. Only `BLOCKED:repair-budget-exhausted` invokes exhaustion closure: after a refused transition, first complete its still-open predecessor with the original completion arguments (`--verdict FAIL` for pre-VERIFY origins; omit `--verdict` for VERIFY), then spawn FINAL_REPORT round zero. If standalone repair spawn was refused after completion, do not complete twice. After other halts, use standalone FINAL_REPORT spawn only if unopened.
362
-
363
- 1. Kill any dev server PHASE 3 left running.
364
-
365
- 2. **FINISH GATE** — run `python3 "$DEVLYN_SHARED_DIR/finish-gate.py"`; branch only on exit code: 0 → clean; 1 or 2 → `BLOCKED:finish-gate-unclean`, and report the `.devlyn/finish-gate.findings.jsonl` listing, including reverted paths. Offenders exit 2 even when every revert succeeded — a silent revert must never ride an exit-0 pass.
366
-
367
- 3. **Terminal verdict** — derive from `state.phases.{plan, implement, surface_close, build_gate, cleanup, verify}.verdict` per the precedence rules in `references/state-schema.md#terminal-verdict`. Verify-only mode short-circuits to `state.phases.verify.verdict`.
368
-
369
- 4. **Render report to `.devlyn/final-report.md` before completion** — first line exactly `<!-- devlyn:final-report run_id=<current state.run_id> -->`, once only, followed by a nonempty report body. Sections: header (run_id, engine, mode, verdict, wall-time), per-phase summary (including SURFACE_CLOSE run or skip, owner PLAN reasoning, and BUILD_GATE/CLEANUP as orchestrator commands with no separate model; code cleanup is included in IMPLEMENT cost), pair/risk-probe status, findings table (verify + finish-gate findings), follow-up notes (the explicit line `pipeline continued to BUILD_GATE — surface_close_rolled_back_adjudication_malformed` when `continued_after_block` is set, any large-mode `## Assumptions` block, any pair-judge TIMEOUT (headline: solo verdict after pair TIMEOUT), any `--no-pair` / `--no-risk-probes` opt-out, and any engine setup guidance after BLOCKED). User-facing text may follow archive; it cannot substitute for this file.
370
-
371
- 5. Complete the span with `state-phase-write.py --devlyn-dir .devlyn --phase final_report complete --verdict <terminal verdict> --log-file .devlyn/final-report.md` BEFORE archive runs (archive prune skips runs whose `final_report.verdict` is null). The writer validates the canonical nonsymlink regular file, current run marker and nonempty body, then binds its exact bytes; validation failure leaves the phase open. Never hand-edit lifecycle fields in `pipeline.state.json` (`references/state-schema.md` § Write protocol).
372
-
373
- 6. **Archive** — invoke the deterministic script: `python3 "$DEVLYN_SHARED_DIR/archive_run.py"`. The script reads `run_id` from `.devlyn/pipeline.state.json`, moves the static per-run artifact set (`PER_RUN_PATTERNS` remains the single ownership list) plus every state-bound process-evidence manifest/raw stream into `.devlyn/runs/<run_id>/`, preserves evidence-relative layout, and rehashes bound bytes before any move. An unsafe/missing/altered evidence path or destination collision reports archive failure without changing the already-derived product verdict. It then best-effort prunes to the 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).
374
-
375
- After successful normal-run archive, return to the outer owner for `references/task-completion.md`. A queue owner first commits its terminal queue transition, then completes once. Honor local-only/no-push; report delivery pending/failure separately from the archived product verdict. This is outside the phase graph.
376
-
377
- ## State management
378
-
379
- `.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 writes it via initial `spawn`, atomic `transition` for direct handoffs, or terminal `complete`; re-entry preserves the prior lifecycle in `history[]` (`references/state-schema.md#write-protocol`). Phase workers never edit `pipeline.state.json` directly.
@@ -1,71 +0,0 @@
1
- # Free-form mode — complexity classifier
2
-
3
- When `/devlyn-resolve` is invoked with a free-form goal (no `--spec`) — whether the goal is the inline positional text or the content of `--goal-file <path>` (PHASE 0 resolves `goal_text` from either source before classifying) — PHASE 0 runs this classifier to set `state.complexity ∈ {trivial, medium, large}` and either proceeds with an internal mini-spec, drafts focused questions for in-prompt resolution, synthesizes a best-effort spec with logged assumptions, or halts zero-scope-signal goals with `/devlyn-ideate` guidance.
4
-
5
- The classifier is rules-based / deterministic — not an LLM judgment call. Decision rules below.
6
-
7
- ## Classification rules
8
-
9
- Compute these signals from the goal text + project state:
10
-
11
- 1. **goal_length** — word count of the user's goal.
12
- 2. **file_scope_signals** — count of file paths or symbol names mentioned in the goal (`bin/cli.js`, `Login.tsx`, `parseArgs`, etc.).
13
- 3. **verb_class** — primary verb of the goal: `fix | add | refactor | debug | review | rewrite | migrate | ...`.
14
- 4. **codebase_size** — `git ls-files | wc -l`. Coarse buckets: `<50` / `<500` / `≥500`.
15
- 5. **has_failing_test** — does the goal mention a specific failing test or include a stack trace?
16
-
17
- Evaluate Large first, then Medium, then Trivial; stop at the first matching branch.
18
-
19
- ### Trivial branch
20
-
21
- Conditions (all must hold):
22
- - `goal_length ≤ 30` words.
23
- - `file_scope_signals ≥ 1` AND `≤ 3`.
24
- - `verb_class ∈ {fix, add}`.
25
- - `has_failing_test == true` OR the goal names a single specific symbol/file.
26
-
27
- Action: synthesize a minimal internal spec from the goal:
28
- - Write `.devlyn/criteria.generated.md` with sections `## Requirements` (the goal as a single bullet, optionally split into 2-3 if obviously separable), `## Out of Scope` ("anything not in the listed files"), `## Verification` (one runnable command if discoverable from the goal — e.g. the failing test, or a smoke command).
29
- - Set `state.complexity = "trivial"`. Proceed to PHASE 1.
30
-
31
- ### Medium branch
32
-
33
- Conditions (any one):
34
- - `goal_length > 30` words.
35
- - `file_scope_signals` between 4 and 10.
36
- - `verb_class ∈ {refactor, debug, review}` AND scope is a single subsystem.
37
- - `has_failing_test == false` but the goal implies a runnable acceptance check.
38
-
39
- Action: synthesize a richer internal spec:
40
- - Read the named files (or grep for the named symbols) to extract 1-2 context anchors (existing patterns, related tests).
41
- - Write `.devlyn/criteria.generated.md` with `## Requirements` (split into 3-5 testable bullets), `## Constraints` (anything implied by the existing patterns), `## Out of Scope` (adjacent code that "looks fixable"), `## Verification` (commands or checks discoverable from existing tests / patterns).
42
- - Set `state.complexity = "medium"`. Proceed to PHASE 1.
43
-
44
- ### Large branch
45
-
46
- Conditions (any one):
47
- - `file_scope_signals > 10` OR zero signals (vague enough that the classifier cannot pick scope).
48
- - `verb_class ∈ {rewrite, migrate}` and scope is multi-subsystem.
49
- - The goal mentions a new feature whose surface area requires design decisions the harness cannot make from a one-shot prompt.
50
-
51
- Action:
52
- - Default: synthesize a best-effort spec from the goal with an explicit `## Assumptions` block (every assumption scope-narrowing and reversible — when in doubt, narrower); log `recommend: /devlyn-ideate first` in `.devlyn/criteria.generated.md` AND the final report; proceed to PHASE 1; the final report flags every assumption for user review.
53
- - Zero-signal exception: if the large classification includes `file_scope_signals == 0` (classifier cannot pick scope), halt with terminal verdict `BLOCKED:large-needs-ideation` — assumptions there would be scope-invention, not narrowing.
54
-
55
- ## Anti-pattern: drift to LLM judgment
56
-
57
- The classifier MUST stay deterministic. If you're tempted to add "and the model assesses whether it's complex" — that is the failure mode this rule exists to prevent. LLM-judgment classifiers swing on prompt-prelude noise; rules over signals do not.
58
-
59
- When the rules are silent (rare — pathological goal text), default to `medium` and proceed.
60
-
61
- ## Mini-spec quality bar
62
-
63
- The internal mini-spec written for trivial / medium / large-assumptions paths must satisfy:
64
-
65
- - `## Requirements` non-empty, each bullet testable (CLI command, test command, observable file change).
66
- - For executable generated verification, put one fenced `json` object with `verification_commands` inside the sentinel-marked `## Verification` section. Generated mode reads this inline carrier, not a sibling file. Follow `../../devlyn-ideate/references/spec-template.md` § "Sibling file: `spec.expected.json`" for constraint coverage, diff scope and violating/allowed controls, but express those checks as executable `verification_commands`; sibling-only fields such as `required_files` and `forbidden_patterns` are rejected inline. Preserve uncovered semantics as source-review obligations.
67
- - `## 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. When all Requirements are pure-design with no runnable acceptance check, put `{"pure_design": true, "verification_commands": []}` in the fenced `json` block and retain semantic source-review obligations. This explicit declaration is required; a missing block, an unmarked empty list, or `pure_design: true` with commands is invalid.
68
- - 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.
69
- - After writing `.devlyn/criteria.generated.md`, set `state.source.type = "generated"`, `state.source.spec_path = null`, `state.source.spec_sha256 = null`, `state.source.criteria_path = ".devlyn/criteria.generated.md"`, and `state.source.criteria_sha256` to the raw-byte SHA-256 of the generated criteria file. Downstream PLAN/IMPLEMENT/VERIFY phases and `spec-verify-check.py --include-risk-probes` depend on this pointer; do not rely on the file existing by convention.
70
-
71
- PLAN reads the mini-spec the same way it reads a real spec. The downstream pipeline cannot tell the difference.
@@ -1,23 +0,0 @@
1
- # Outer-loop commit boundary
2
-
3
- A full `/devlyn-resolve` run starts only from committed owner inputs. Keep each
4
- commit scoped: no unrelated paths or queue-item changes.
5
-
6
- 1. Before task writes, the outer owner allocates an absent task branch per
7
- [task completion](task-completion.md).
8
- Never adopt a pre-existing task branch. Before an item's first full run, commit the current queue-item delta, when
9
- applicable, together with its linked spec bundle. For conversational work,
10
- commit the agreed spec bundle before the full run.
11
- 2. When findings require an accepted verification amendment, commit only that
12
- linked spec-bundle amendment before rerunning the item.
13
- 3. After a terminal result, commit the current item's `[x]` or `[F]` queue
14
- transition before starting the next pending item.
15
- 4. After mandatory successful normal-run archive and any terminal queue commit,
16
- the outer owner completes delivery once per [task completion](task-completion.md).
17
- Bind the verified source and declared queue-only commit separately. Failed or
18
- verify-only runs do not publish; local-only/no-push wins. Pending delivery
19
- retains its workspace and never changes the product verdict.
20
-
21
- Drain strictly serially. Bootstrap and resolve never make these owner commits;
22
- the outer-loop driver owns them. Return queue custody before completion; never
23
- advance onto a retained pending task branch.
@@ -1,75 +0,0 @@
1
- # PHASE 3 — BUILD_GATE (canonical body)
2
-
3
- The orchestrator reads this body directly, without an adapter header or worker prompt. BUILD_GATE runs the same commands CI / Docker / production run; gate selection and diagnosis remain orchestrator work.
4
-
5
- <role>
6
- Run language-specific gates and the spec literal-match verification. Emit findings; the orchestrator's fix loop consumes them. Git staging and commits belong to the parent IMPLEMENT checkpoint; verify any staging prerequisite with read-only Git inspection, never repeat `git add` or forward task staging instructions as worker actions.
7
- </role>
8
-
9
- <capability_contract>
10
- Before executing gates, inspect the orchestrator command route's effective filesystem, subprocess, loopback, PTY and network capabilities and record the actual basis in `.devlyn/build_gate.log.md`. Use an already authorized CI-equivalent route; do not infer capabilities from an engine name or unrelated command success, narrow the route, or widen permissions. No child receipt attests these parent capabilities.
11
-
12
- If the parent route or an authoritative tool response explicitly denies a
13
- required operation before the command can produce product output:
14
-
15
- 1. Preserve the exact original command, denied operation, and raw denial through
16
- the shared `process-evidence.py` manifest for this BUILD_GATE run/round. Use
17
- its `capability_denied` classification; a matching stderr phrase is never
18
- sufficient classification.
19
- 2. Stop with `BLOCKED:build-env-underprovisioned`, citing
20
- `operation=<filesystem|subprocess|loopback|pty|network>`, the command, and the
21
- manifest path plus evidence id.
22
- 3. Do not emit a product finding, run a substitute command, narrow the command,
23
- retry it on another route, or treat the denial as test/lint output.
24
-
25
- If a denial is appended after `.devlyn/spec-verify.results.json` was sealed, refresh only that existing object's `process_evidence` and `commands` before completion. Load `$DEVLYN_SHARED_DIR/process-evidence.py` with Python's `runpy.run_path`; using the current work root, `state.run_id`, `"build_gate"`, and its round, call `validate_manifest(work, manifest_relative_path(state, "build_gate"), run_id, "build_gate", round_, require_expectations=False)`, then `bound_carrier_summary_commands(work, carrier)` and `validate_summary_commands(work, commands, carrier)`. Replace only those two result fields with the returned carrier/commands. Preserve all other fields, genuine findings, failed expectations and raw streams; do not rewrite the manifest or replay commands. This is conditional on an actual append, not routine browser resealing. If no results file exists, retain the existing BLOCKED completion path.
26
-
27
- The state writer derives the phase verdict floor from this sealed manifest. A
28
- capability denial can complete only as `BLOCKED`; a failed product expectation
29
- cannot complete as `PASS` or `PASS_WITH_ISSUES` even if a mutable results or
30
- findings file claims otherwise.
31
-
32
- An executed command that exits nonzero remains a genuine product result even if
33
- its stdout/stderr contains text such as `permission denied` or `operation not
34
- permitted`. Preserve the existing finding rules below. Only explicit route/tool
35
- capability evidence produces the blocker.
36
- </capability_contract>
37
-
38
- <detection>
39
- Detect the project shape from files in `state.base_ref.sha`:
40
-
41
- - `package.json` → Node. Use the declared package manager; default `npm`. If `tsconfig.json` exists → run `tsc --noEmit`.
42
- - `pyproject.toml` / `requirements.txt` → Python. If `pyproject.toml` declares a tool config (`ruff`, `mypy`, `pytest`), run the declared tool.
43
- - `go.mod` → Go. Run `go build ./... && go vet ./... && go test ./...`.
44
- - `Cargo.toml` → Rust. Run `cargo build && cargo clippy && cargo test`.
45
- - Mixed / monorepo: detect per-workspace; run only against changed workspaces (use `git diff --name-only <state.base_ref.sha>`).
46
- </detection>
47
-
48
- <gates>
49
- Resolve the concrete commands for steps 1–3; detection examples are not command identities. Run them in order unless step 4's current valid literal contract, using its source precedence and checker defaults, schedules the exact command with the same source/test inputs, working directory and effective environment. Establish the work root and normalized child environment from the checker/runner call sites and the current invocation; a manifest's command string alone does not establish them. The literal must expect exit 0, cover every gate assertion and use the same effective timeout as the planned gate invocation; uncertain matches run normally. Defer a matching gate to step 4 and derive its usual per-error findings and severity from that invocation's validated current BUILD_GATE run/round raw streams, citing the evidence id. A completed nonzero exit still supplies the gate's usual failure findings. If step 4 leaves no validated exit-kind result for a deferred gate, run that gate normally and append its findings, preserving step 4's failures; capability denials retain the stop contract above. Keep every literal invocation and independent post-CLEANUP VERIFY. Each gate emits findings into `.devlyn/build_gate.findings.jsonl`:
50
-
51
- 1. **Type check** (TypeScript / mypy / etc.). Each error → one finding, severity `HIGH`, rule `correctness.type-check`.
52
- 2. **Lint**. Run a recognized configured language linter (for example, ESLint with a config, `ruff`, or `clippy`) and/or the exact `package.json` `scripts.lint` command. A declared specialized `lint:*` script (for example, `lint:json`) is not the general language lint gate on the strength of its declaration alone. If the spec's verification commands explicitly name such a specialized command, step 4 executes it and preserves its real failure severity. If no linter is applicable, log an explicit SKIP in `.devlyn/build_gate.log.md` and emit zero lint findings. Each error → finding, severity `MEDIUM`, rule `quality.lint`. Warnings stay LOW unless the spec elevates them.
53
- 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.
54
- 4. **Spec literal verification + risk probes**: `python3 "$DEVLYN_SHARED_DIR/spec-verify-check.py" --include-risk-probes`. The script routes every literal command and risk probe through `process-evidence.py`, preserving exact command, outcome, raw stdout/stderr, and digests in the BUILD_GATE run/round manifest. It 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. When probes are enabled or `.devlyn/risk-probes.jsonl` exists, missing or mismatched top-level `state.risk_probes_digest` emits `correctness.risk-probe-integrity` CRITICAL; the digest binds `.devlyn/risk-probes.jsonl` plus referenced `.devlyn/probes/` script bytes. 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 and untracked delta: any changed path or created-during-run untracked path outside the declared surface → `scope.out-of-scope-file` CRITICAL (`.devlyn/` exempt) — the only sanctioned fix is removing the file, never widening the declared surface, since that would let the same worker that leaked scope self-authorize the leak; missing sentinel, malformed `plan.md`/surface block, or missing `.devlyn/untracked.baseline` → `scope.authorized-surface-malformed` CRITICAL. This scope check runs only here (BUILD_GATE), not when VERIFY MECHANICAL reuses this script post-CLEANUP, while owner CLEANUP separately enforces unchanged tracked source and the final untracked baseline. Historical worker CLEANUP retains its existing allowlist.
55
- 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`.
56
-
57
- Append all findings; do not stop on the first failure.
58
- </gates>
59
-
60
- <output>
61
- - `.devlyn/build_gate.findings.jsonl` — JSONL stream, one finding per line. Schema: `{id, rule_id, severity, file, line, message, fix_hint, criterion_ref}`.
62
- - A completed checker preflight rejection writes `spec-verify.results.json` with `preflight_failure` and the original CRITICAL finding's path/digest. Retain that source (default `.devlyn/spec-verify-findings.jsonl`) and append its finding to `.devlyn/build_gate.findings.jsonl`. Earlier gate observations remain bound, but no spec command is claimed to have run. An absent results file still follows the interrupted-observation `BLOCKED` path.
63
- - `.devlyn/build_gate.log.md` — human-readable gate results with raw output or links to complete raw-output files, using sealed artifacts where available. Retain complete inventories/hash maps in `.devlyn/` files; report their paths, digests and relevant deltas instead of dumping them into the conversation.
64
- - Report `PASS` if zero CRITICAL/HIGH findings, `FAIL` for genuine product findings, or `BLOCKED:build-env-underprovisioned` only for the evidence-backed capability path above. A capability blocker leaves no product finding. Do not edit `pipeline.state.json` yourself — the orchestrator records it via `state-phase-write.py` from these artifacts.
65
- </output>
66
-
67
- <quality_bar>
68
- - Same commands every time. Configuration drift between this gate and CI is a defect; raise as a finding rather than soften this gate. A package-script declaration alone is not evidence that CI executes it.
69
- - Forbidden-pattern check (regex against `git diff`) for `spec.expected.json.forbidden_patterns` runs as part of step 4. Disqualifier-severity matches → CRITICAL findings.
70
- - Reporter artifacts the gate generates (Playwright traces, coverage HTML) belong in gitignored paths. If they leak into `git diff --stat`, flag as `scope.tooling-artifact-leak` MEDIUM and let the fix loop / cleanup handle removal.
71
- </quality_bar>
72
-
73
- <runtime_principles>
74
- The gate is mechanical — its discipline is "do not skip a check, do not paraphrase a verification command, do not narrow severity to mute noise." Findings drive the fix loop; muting findings without a justified spec exception is a workaround.
75
- </runtime_principles>