devlyn-cli 2.6.1 → 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 (81) hide show
  1. package/AGENTS.md +10 -72
  2. package/CLAUDE.md +29 -12
  3. package/README.md +7 -5
  4. package/benchmark/auto-resolve/fixtures/F1-cli-trivial-flag/task.ko.txt +8 -0
  5. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/NOTES.md +28 -0
  6. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/RETIRED.md +15 -0
  7. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/expected.json +57 -0
  8. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/metadata.json +10 -0
  9. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/setup.sh +50 -0
  10. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/spec.md +59 -0
  11. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/task.txt +9 -0
  12. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/verifiers/ordered-settlement.js +88 -0
  13. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/verifiers/reject-invalid.js +45 -0
  14. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/NOTES.md +30 -0
  15. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/RETIRED.md +16 -0
  16. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/expected.json +64 -0
  17. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/metadata.json +10 -0
  18. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/setup.sh +21 -0
  19. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/spec.md +58 -0
  20. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/task.txt +9 -0
  21. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/verifiers/dup-op.js +54 -0
  22. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/verifiers/priority-apply.js +82 -0
  23. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/verifiers/rollback.js +77 -0
  24. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/NOTES.md +99 -0
  25. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/RETIRED.md +18 -0
  26. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/expected.json +64 -0
  27. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/metadata.json +10 -0
  28. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/setup.sh +4 -0
  29. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/spec.md +61 -0
  30. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/task.txt +9 -0
  31. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/verifiers/correctness-small.js +135 -0
  32. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/verifiers/scale.js +100 -0
  33. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/verifiers/validation.js +75 -0
  34. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/NOTES.md +93 -0
  35. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/RETIRED.md +16 -0
  36. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/expected.json +65 -0
  37. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/metadata.json +10 -0
  38. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/setup.sh +18 -0
  39. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/spec.md +66 -0
  40. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/task.txt +9 -0
  41. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/verifiers/correctness-small.js +84 -0
  42. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/verifiers/scale.js +184 -0
  43. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/verifiers/validation.js +74 -0
  44. package/benchmark/auto-resolve/scripts/pair-rejected-fixtures.sh +12 -0
  45. package/benchmark/auto-resolve/scripts/run-fixture.sh +39 -1
  46. package/benchmark/auto-resolve/scripts/test-lint-fixtures.sh +1 -1
  47. package/bin/devlyn.js +71 -83
  48. package/config/skills/_shared/adapters/README.md +7 -0
  49. package/config/skills/_shared/adapters/claude.md +49 -2
  50. package/config/skills/_shared/adapters/omp.md +17 -0
  51. package/config/skills/_shared/archive_run.py +8 -2
  52. package/config/skills/_shared/codex-config.md +22 -6
  53. package/config/skills/_shared/engine-doctor.sh +107 -0
  54. package/config/skills/_shared/engine-preflight.md +18 -11
  55. package/config/skills/_shared/expected.schema.json +26 -0
  56. package/config/skills/_shared/pair-plan-schema.md +2 -2
  57. package/config/skills/_shared/runtime-principles.md +1 -1
  58. package/config/skills/_shared/spec-verify-check.py +773 -486
  59. package/config/skills/_shared/state-phase-write.py +374 -0
  60. package/config/skills/_shared/verify-merge-findings.py +177 -61
  61. package/config/skills/devlyn:engines/SKILL.md +47 -0
  62. package/config/skills/devlyn:ideate/SKILL.md +22 -3
  63. package/config/skills/devlyn:ideate/references/elicitation.md +2 -2
  64. package/config/skills/devlyn:ideate/references/from-spec-mode.md +2 -2
  65. package/config/skills/devlyn:ideate/references/project-mode.md +1 -1
  66. package/config/skills/devlyn:ideate/references/spec-template.md +15 -4
  67. package/config/skills/devlyn:queue/SKILL.md +26 -0
  68. package/config/skills/devlyn:resolve/SKILL.md +55 -25
  69. package/config/skills/devlyn:resolve/references/free-form-mode.md +1 -1
  70. package/config/skills/devlyn:resolve/references/phases/build-gate.md +2 -2
  71. package/config/skills/devlyn:resolve/references/phases/cleanup.md +1 -1
  72. package/config/skills/devlyn:resolve/references/phases/implement.md +1 -1
  73. package/config/skills/devlyn:resolve/references/phases/plan.md +3 -2
  74. package/config/skills/devlyn:resolve/references/phases/probe-derive.md +8 -0
  75. package/config/skills/devlyn:resolve/references/phases/verify.md +18 -9
  76. package/config/skills/devlyn:resolve/references/state-schema.md +11 -7
  77. package/optional-skills/devlyn:pencil-pull/SKILL.md +5 -0
  78. package/optional-skills/devlyn:pencil-push/SKILL.md +5 -0
  79. package/package.json +1 -2
  80. package/scripts/lint-skills.sh +55 -46
  81. package/agents-config/evaluator.md +0 -64
package/AGENTS.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  devlyn-cli installs `/devlyn:ideate` (optional) and `/devlyn:resolve` (required) into Claude Code, plus the contract below. Codex CLI reads this file when invoked inside a project that has it. The principles are non-negotiable on every change.
4
4
 
5
+ ## North Star
6
+
7
+ This contract serves one goal: any capable engine — Claude, GPT/Codex, or a future adapter-equipped model — takes a user's intent (prompt, spec, or queue entry) end-to-end to shipped, engineer-quality software, hands-free, with consistent quality across engines. The harness must measurably out-earn bare prompting, and every added layer (pair mode, probes, gates) must out-earn the layer below it. When rules below conflict or feel ambiguous in context, resolve toward this goal.
8
+
5
9
  ## Core principles
6
10
 
7
11
  Seven rules govern every change. Cite them by name when a decision touches one.
@@ -32,6 +36,10 @@ ideate (optional) -> resolve -> ship
32
36
 
33
37
  Each skill's `SKILL.md` is the source of truth for flags and workflow. Do not duplicate.
34
38
 
39
+ Engine roles: orchestrator = whichever CLI the user opened (this contract is symmetric with CLAUDE.md; measured status 2026-07-05: Claude Code + omp run the full phase-gated pipeline; in the iter-0061 minimal-repo trivial-add shape on this machine, Codex CLI also ran it when this AGENTS.md was present in the project (ordinary invocation, 4/4); without project AGENTS.md, the same shape silently skipped the pipeline (iter-0040 F6, 4/4). If you are Codex orchestrating `/devlyn:resolve`, the phase machinery is mandatory regardless of task size; if you will not run it, say so explicitly and stop — do not silently degrade to ad-hoc execution); executor (PLAN/IMPLEMENT/CLEANUP) defaults to `claude`, overridable per run with `--engine <name>` or durably via machine-local `.devlyn/engines.json` `{"executor": "<name>", "pair_judge_priority": ["<name>", ...]}`; pair judge = first available OTHER engine. Pins fail closed (`BLOCKED:<engine>-unavailable` / `BLOCKED:invalid-engine-config`); new engines plug in by shipping `_shared/adapters/<name>.md`. `/devlyn:engines` (no args) shows the role table + detected engines; `executor <name>` / `pair <name>,...` / `clear` manage the pins.
40
+
41
+ Conversational handoff + loop engineering (default entry): the orchestrating model — not the user — invokes the skills. `/devlyn:queue` fronts the intent queue (`docs/specs/queue.md`): no args = status, `add <intent>`, `drain` = serial unattended drain per the contract below. Large agreed work is written to `docs/specs/<id>/spec.md` (always a spec file; `--goal-file` is trivial/medium-only), summarized once for user review, then run hands-free via `--spec`. Outer loop per task: findings-backed verdicts (NEEDS_WORK, verify/build-gate exhaustion) get spec amendment + re-run, max 3 iterations; infrastructure / invalid-input / engine-availability / implement-empty BLOCKED verdicts surface immediately. Unattended queue drain (`docs/specs/queue.md`, strictly serial): assumptions may only take scope-narrowing, reversible, non-user-visible defaults — material ambiguity marks the item `[F] needs-review` and the drain continues.
42
+
35
43
  ## Subtractive-First Editing
36
44
 
37
45
  Before writing any change, answer in this order:
@@ -56,7 +64,7 @@ Default mode is execution toward the user's stated goal. Do not drift.
56
64
  Refuse these patterns:
57
65
 
58
66
  - Unrequested work ("while here, also fix..."). Pre-existing dead code → mention only, do NOT delete. Orphans YOUR change created (now-unused imports/variables/functions) → clean them up.
59
- - Tangential cleanup in files the task does not require. Match existing style even if you'd write it differently; do NOT touch comments, formatting, or code orthogonal to your real change — silent side-effects on neighboring lines are the most common Karpathy-observed regression class.
67
+ - Tangential cleanup in files the task does not require. Match existing style even if you'd write it differently; on touched lines, replace only the bytes the task requires and preserve all other bytes, comments, formatting, and orthogonal code.
60
68
  - Speculative robustness for cases not observed in production, tests, findings, or the spec.
61
69
  - Mid-flight re-scoping without user approval.
62
70
  - Curiosity exploration that is not on the critical path.
@@ -85,6 +93,7 @@ Every finding cites concrete evidence:
85
93
  - Browser: route/URL + screenshot or observed evidence.
86
94
  - Benchmark: run id, fixture id, metric, raw result path.
87
95
  - Negative existence ("X lacks Y", "X cannot Z", "X is Y-specific"): highest-risk claim shape — fails to any single counter-example. Active search required at write time, not absence-of-memory. Applies to chat responses and trade-off tables, not only formal findings.
96
+ - Position reversal in an oracle-less debate (design, strategy, trade-off): a reversal is itself a claim. Cite the NAMED DELTA — the specific prior claim, evidence, or criterion that changed — before flipping. Flipping to the last speaker without a cited delta is capitulation, not reasoning; genuinely unresolved disagreement escalates to the user.
88
97
 
89
98
  Exclude vague claims. They produce vague fixes.
90
99
 
@@ -103,74 +112,3 @@ Exclude vague claims. They produce vague fixes.
103
112
  - Be concise and specific.
104
113
  - State blockers plainly.
105
114
  - Separate completed work, verification, and remaining risks.
106
-
107
-
108
- ---
109
-
110
- # Devlyn Agent Instructions
111
-
112
- # Evaluator Agent
113
-
114
- You are a code quality evaluator. Your job is to audit work produced by another session, PR, or changeset and provide evidence-based findings with exact file:line references.
115
-
116
- ## Before You Start
117
-
118
- 1. **Check for done criteria**: Read `.devlyn/done-criteria.md` if it exists. When present, this is your primary grading rubric — every criterion must be verified with evidence. When absent, fall back to the checklists below.
119
-
120
- ## Calibration
121
-
122
- You will be too lenient by default. You will identify real issues, then talk yourself into deciding they aren't a big deal. Fight this tendency.
123
-
124
- **Rule**: When in doubt, score DOWN. A false negative ships broken code. A false positive costs minutes of review. The cost is asymmetric.
125
-
126
- - A catch block that logs but doesn't surface error to user = HIGH (not MEDIUM). Logging is not error handling.
127
- - A `let` that could be `const` = LOW note only. Linters catch this.
128
- - "The error handling is generally quite good" = WRONG. Count the instances. Name the files.
129
-
130
- ## Evaluation Process
131
-
132
- 1. **Discover scope**: Read the changeset (git diff, PR diff, or specified files)
133
- 2. **Assess correctness**: Find bugs, logic errors, silent failures, missing error handling
134
- 3. **Check architecture**: Verify patterns match existing codebase, no type duplication, proper wiring
135
- 4. **Verify spec compliance**: If a spec exists (HANDOFF.md, RFC, issue, done-criteria.md), compare requirements vs implementation
136
- 5. **Check error handling**: Every async operation needs loading, error, and empty states in UI. No silent catches.
137
- 6. **Review API contracts**: New endpoints must follow existing conventions for naming, validation, error envelopes
138
- 7. **Assess test coverage**: New modules need tests. Run the test suite and report results.
139
- 8. **Evaluate product quality**: Does this feel like a real feature or a demo stub? Are workflows complete end-to-end? Is the UI coherent?
140
-
141
- ## Rules
142
-
143
- - Every finding must have a file:line reference. No guesswork.
144
- - Classify by severity: CRITICAL (must fix), HIGH (should fix), MEDIUM (fix or justify), LOW (note)
145
- - Call out what's done well, not just problems
146
- - Look for cross-cutting patterns (e.g., same mistake repeated in multiple files)
147
-
148
- ## Output
149
-
150
- Write findings to `.devlyn/EVAL-FINDINGS.md` for downstream consumption:
151
-
152
- ```markdown
153
- # Evaluation Findings
154
-
155
- ## Verdict: [PASS / PASS WITH ISSUES / NEEDS WORK / BLOCKED]
156
-
157
- ## Done Criteria Results (if done-criteria.md existed)
158
- - [x] [criterion] — VERIFIED: [evidence]
159
- - [ ] [criterion] — FAILED: [what's wrong, file:line]
160
-
161
- ## Findings Requiring Action
162
- ### CRITICAL
163
- - `file:line` — [description] — Fix: [suggested approach]
164
-
165
- ### HIGH
166
- - `file:line` — [description] — Fix: [suggested approach]
167
-
168
- ## Cross-Cutting Patterns
169
- - [pattern description]
170
-
171
- ## What's Good
172
- - [positive observations]
173
- ```
174
-
175
- Do NOT delete `.devlyn/done-criteria.md` or `.devlyn/EVAL-FINDINGS.md` — the orchestrator or user is responsible for cleanup.
176
-
package/CLAUDE.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  devlyn-cli installs `/devlyn:ideate` (optional) and `/devlyn:resolve` (required) into Claude Code, plus the contract below. These principles are non-negotiable on every change — yours and any sub-agent's.
4
4
 
5
+ ## North Star
6
+
7
+ This contract serves one goal: any capable engine — Claude, GPT/Codex, or a future adapter-equipped model — takes a user's intent (prompt, spec, or queue entry) end-to-end to shipped, engineer-quality software, hands-free, with consistent quality across engines. The harness must measurably out-earn bare prompting, and every added layer (pair mode, probes, gates) must out-earn the layer below it. When rules below conflict or feel ambiguous in context, resolve toward this goal.
8
+
5
9
  ## Core principles
6
10
 
7
11
  Seven rules govern every change. Cite them by name when a decision touches one.
@@ -24,22 +28,35 @@ The runtime sub-agent contract below (Subtractive-first / Goal-locked / No-worka
24
28
 
25
29
  ## Quick Start
26
30
 
27
- Two skills cover the full cycle post iter-0034 Phase 4 cutover (2026-05-04). `/devlyn:ideate` is OPTIONAL; `/devlyn:resolve` is REQUIRED; `/devlyn:design-ui` is also REQUIRED as the creative UI exploration surface. **Both pipeline skills default to `--engine claude`** for PLAN/IMPLEMENT. Codex BUILD/IMPLEMENT and PLAN-pair remain research-only, but `/devlyn:resolve` VERIFY has conditional-default pair-JUDGE when its `SKILL.md` trigger policy fires. Pass `--engine auto` or `--engine codex` explicitly to opt into the broader research path. If a selected or conditionally required engine is unavailable, the run stops with `BLOCKED:<engine>-unavailable` and setup guidance.
31
+ Two skills cover the full cycle. `/devlyn:ideate` is OPTIONAL; `/devlyn:resolve` is REQUIRED; `/devlyn:design-ui` is also REQUIRED as the creative UI exploration surface. Engine selection follows the role map below.
28
32
 
29
33
  1. `/devlyn:ideate` (optional) — unstructured idea → `docs/specs/<id>/spec.md` + `spec.expected.json`. Modes: default Q&A, `--quick` (autonomous-pipeline-safe), `--from-spec <path>`, `--project`.
30
34
  2. `/devlyn:resolve` — hands-free pipeline for any coding task. Free-form goal, `--spec <path>`, or `--verify-only <diff> --spec <path>`. Phases: PLAN → IMPLEMENT → BUILD_GATE → CLEANUP → VERIFY (fresh subagent, findings-only).
31
35
 
32
36
  Each skill's `SKILL.md` is the source of truth for its flags and workflow — don't duplicate them here.
33
37
 
34
- ### When to use which
38
+ ### Engine roles — auto-detected, manually pinnable
39
+
40
+ | Role | Default | Manual override |
41
+ |---|---|---|
42
+ | Orchestrator — conversation, handoff, loop driving | whichever CLI you open (contract is symmetric: CLAUDE.md ↔ AGENTS.md). Measured status 2026-07-05: Claude Code + omp run the full phase-gated pipeline; in the iter-0061 minimal-repo trivial-add shape on this machine, Codex CLI also ran it when the project carried the devlyn AGENTS.md (ordinary invocation, 4/4); without project AGENTS.md, the same shape silently skipped the pipeline (iter-0040 F6, 4/4) | switch CLIs; the file artifacts (spec/queue/state) carry over |
43
+ | Executor — PLAN/IMPLEMENT/CLEANUP + primary VERIFY judge | `claude` | `--engine <name>` per run, or `/devlyn:engines executor <name>` (durable pin) |
44
+ | Pair judge — VERIFY pair-JUDGE, risk probes | first available OTHER engine (claude↔codex) | `/devlyn:engines pair <name>,...`; `--no-pair` opts out |
45
+
46
+ `/devlyn:engines` with no args shows the current role table, detected engines, and how to pin or clear — the pins live in `.devlyn/engines.json`.
47
+
48
+ `.devlyn/engines.json` is machine-local — not committed, not archived. Pins are promises: a pinned unavailable engine stops with `BLOCKED:<engine>-unavailable`; a name without a `_shared/adapters/<name>.md` adapter stops with `BLOCKED:invalid-engine-config`. New engines (GLM, pi-agent backends) plug in by shipping an adapter file — no skill changes. Codex BUILD/IMPLEMENT and PLAN-pair remain research-only paths behind explicit `--engine codex`.
49
+
50
+ ### Conversational handoff + loop engineering — the default entry for all work
51
+
52
+ The user does not invoke skills manually; the orchestrating model does. Small tasks: invoke `/devlyn:resolve "<goal>"` directly. Large tasks agreed in conversation:
53
+
54
+ 1. Write the agreed contract to `docs/specs/<id>/spec.md` (+ `spec.expected.json` when mechanical verifications exist). Always a spec file for large work — never `--goal-file`, which routes into `BLOCKED:large-needs-ideation`.
55
+ 2. Present a one-screen plan-contract summary — the user's single review checkpoint, BEFORE the pipeline starts.
56
+ 3. On go-ahead, invoke `/devlyn:resolve --spec <path>` hands-free to completion.
57
+ 4. **Per-task outer loop**: read the terminal verdict. PASS → done. Verdicts backed by spec/verification findings (NEEDS_WORK, verify/build-gate exhaustion) → adjudicate the findings, amend the spec (recorded in the spec file; spec stays read-only inside a run), re-invoke — at most 3 outer iterations, then surface with the findings trail. Infrastructure, invalid-input, engine-availability, and implement-empty BLOCKED verdicts are not spec-amendable: surface immediately. Every iteration re-enters through durable artifacts (spec, findings, run archive), never conversation memory.
35
58
 
36
- | Situation | Command |
37
- |-----------|---------|
38
- | New project / multi-feature plan / external spec to normalize | `/devlyn:ideate` (`--project`, default, or `--from-spec`) |
39
- | One-line goal you want turned into a spec autonomously | `/devlyn:ideate --quick` |
40
- | Spec already in hand | `/devlyn:resolve --spec <path>` |
41
- | Free-form fix / feature / refactor / debug / PR review | `/devlyn:resolve "<describe the work>"` |
42
- | Verify a diff or PR against a spec | `/devlyn:resolve --verify-only <ref> --spec <path>` |
59
+ **Intent queue (unattended drain)**: the user stacks intents in `docs/specs/queue.md` (ordered checklist; `/devlyn:queue` is the front-end — no args shows status, `add <intent>` appends, `drain` starts the serial drain). Drain strictly serially — per item: spec it, run the outer loop, mark `[x]` done or `[F]` blocked with reason, continue; a blocked item never halts the queue. The queue entry is the user's go-ahead, so assume-and-log replaces the interactive checkpoint — but unattended assumptions may only take scope-narrowing, reversible, non-user-visible defaults. Material ambiguity (user-visible behavior, data/state semantics, new files/scripts/flags, implementation surface) → mark `[F] needs-review` and move on. End the drain with a per-item verdict + assumptions report.
43
60
 
44
61
  ### Subtractive-first editing — perfection = nothing left to remove
45
62
  <!-- runtime-principles:section=subtractive-first:begin -->
@@ -87,7 +104,7 @@ This rule exists because LLMs (including you) are trained to be helpful, compreh
87
104
  **The five drift patterns you must refuse to execute on:**
88
105
 
89
106
  1. **Unrequested work.** "While I'm here, I noticed X is broken/ugly/inefficient" → **stop**. The user did not ask for X. If X is a real defect, surface it as a finding, a follow-up suggestion, or an entry in a TODO list — do NOT fix it inside the current change. Mixing unrequested work with requested work is what makes diffs unreviewable and PRs eternal. **Pre-existing dead code → mention only, do NOT delete; orphans YOUR change created (now-unused imports, variables, functions) → clean them up.**
90
- 2. **Tangential cleanup.** "This file looks messy, let me also tidy..." → **stop**. The current task is the only task. Unrelated cleanup is a separate change requiring its own justification, scope, and pre-flight 0 check. **Match existing style even if you'd write it differently; do NOT touch comments, formatting, or code orthogonal to your real change** — silent side-effects on neighboring lines are the most common Karpathy-observed regression class.
107
+ 2. **Tangential cleanup.** "This file looks messy, let me also tidy..." → **stop**. The current task is the only task. Unrelated cleanup is a separate change requiring its own justification, scope, and pre-flight 0 check. **Match existing style even if you'd write it differently; on touched lines, replace only the bytes the task requires and preserve all other bytes, comments, formatting, and orthogonal code.**
91
108
  3. **Speculative robustness.** "Just adding a check / fallback / handler for the case where..." → **stop**. If the case has not been observed (in production, in tests, in a finding), it does not belong in this change. Defensive code added for unobserved cases is the most common form of accretion debt — it never gets removed because nobody can prove the case never happens.
92
109
  4. **Re-scoping mid-flight.** "Actually, the better way to do this is to also restructure / rename / migrate..." → **stop**. If you discover the requested approach is wrong, surface that to the user with evidence and let them adjudicate. Do NOT silently expand scope. The user's explicit redirect is the only authorization to enlarge a task.
93
110
  5. **Curiosity detours.** "Let me also explore how Y works to understand this better..." → **stop**, unless Y is provably on the goal's critical path. Curiosity-driven exploration is creative-mode; default is execution-mode.
@@ -135,7 +152,7 @@ A finding without one of these forms is excluded. Vague findings produce vague f
135
152
 
136
153
  ## Codex invocation
137
154
 
138
- When `/devlyn:resolve` or `/devlyn:ideate` route a phase to Codex (`--engine codex`, `--engine auto`, or conditional VERIFY pair/risk-probe routing), the wrapper-form contract lives in `config/skills/_shared/codex-config.md` (or `.claude/skills/_shared/codex-config.md` once installed). Omit `-m <model>` — the CLI's current flagship is used automatically. MCP is not in the loop. If Codex is required and unavailable, stop with `BLOCKED:codex-unavailable` and setup guidance.
155
+ When `/devlyn:resolve` or `/devlyn:ideate` route a phase to Codex (`--engine codex` or conditional VERIFY pair/risk-probe routing), the wrapper-form contract lives in `config/skills/_shared/codex-config.md` (or `.claude/skills/_shared/codex-config.md` once installed). Omit `-m <model>` — the CLI's current flagship is used automatically. MCP is not in the loop. If Codex is required and unavailable, stop with `BLOCKED:codex-unavailable` and setup guidance.
139
156
 
140
157
  ## Working Mode
141
158
 
@@ -146,7 +163,7 @@ When `/devlyn:resolve` or `/devlyn:ideate` route a phase to Codex (`--engine cod
146
163
 
147
164
  ## Skill Boundary Policy
148
165
 
149
- The runtime pipeline surface is two skills — `/devlyn:resolve` and `/devlyn:ideate` — plus `/devlyn:design-ui` for creative UI exploration. `/devlyn:resolve` runs PLAN → IMPLEMENT → BUILD_GATE → CLEANUP → VERIFY inline; verification, cleanup, and security review (delegated to the native `security-review` Claude Code skill from BUILD_GATE) all live inside the pipeline. There are no standalone `/devlyn:review`, `/devlyn:evaluate`, or `/devlyn:team-resolve` surfaces. `/devlyn:design-ui` spawns a 5-specialist design team (Creative Director, Product Designer, Visual Designer, Interaction Designer, Accessibility Designer). `/devlyn:reap` is an optional user-invoked skill in `optional-skills/`; resolve never delegates to it.
166
+ The runtime pipeline surface is two skills — `/devlyn:resolve` and `/devlyn:ideate` — plus `/devlyn:design-ui` for creative UI exploration and two utilities added on explicit user direction: `/devlyn:engines` (engine-role config, iter-0038) and `/devlyn:queue` (intent-queue status/add/drain, iter-0039). `/devlyn:resolve` runs PLAN → IMPLEMENT → BUILD_GATE → CLEANUP → VERIFY inline; verification, cleanup, and security review (delegated to the native `security-review` Claude Code skill from BUILD_GATE) all live inside the pipeline. There are no standalone `/devlyn:review`, `/devlyn:evaluate`, or `/devlyn:team-resolve` surfaces. `/devlyn:design-ui` spawns a 5-specialist design team (Creative Director, Product Designer, Visual Designer, Interaction Designer, Accessibility Designer). `/devlyn:reap` is an optional user-invoked skill in `optional-skills/`; resolve never delegates to it.
150
167
 
151
168
  Browser validation runs directly from BUILD_GATE using whichever toolchain is available (Chrome MCP, Playwright, or curl-tier fallback) — there is no separate `/devlyn:browser-validate` skill.
152
169
 
package/README.md CHANGED
@@ -27,7 +27,7 @@ If devlyn-cli saved you time, [give it a star](https://github.com/fysoul17/devly
27
27
  npx devlyn-cli
28
28
  ```
29
29
 
30
- That's it. The installer opens with a single **agent selector** — pick any combination of **Claude Code**, **Codex CLI**, **oh-my-pi (omp)**, **Pi**, Gemini, Cursor, Copilot, or Windsurf, and devlyn installs into every one you choose in a single pass. Claude Code and any agent already present on your machine are pre-checked, so the common case is just Enter. Skill-capable agents receive the `devlyn:resolve`, `devlyn:ideate`, and `devlyn:design-ui` skills in the directory each one loads from: Codex → `~/.codex/skills/`, while **omp and Pi share `~/.agents/skills/`** — the cross-agent standard both read — so the bundle is written there once, not duplicated per agent. In Codex / omp / Pi, invoke them as skills (`$devlyn:resolve`, `$devlyn:ideate`, `$devlyn:design-ui`); in Claude Code they're slash commands (`/devlyn:resolve`). Run it again anytime to update. (`npx devlyn-cli -y` installs the Claude core non-interactively; `npx devlyn-cli agents <cli>` adds one agent later.)
30
+ That's it. The installer opens with a single **agent selector** — pick any combination of **Claude Code**, **Codex CLI**, **oh-my-pi (omp)**, or **Pi**, and devlyn installs into every one you choose in a single pass. Claude Code and any agent already present on your machine are pre-checked, so the common case is just Enter. Skill-capable agents receive the `devlyn:resolve`, `devlyn:ideate`, and `devlyn:design-ui` skills in the directory each one loads from: Codex → `~/.codex/skills/`, while **omp and Pi share `~/.agents/skills/`** — the cross-agent standard both read — so the bundle is written there once, not duplicated per agent. In Codex / omp / Pi, invoke them as skills (`$devlyn:resolve`, `$devlyn:ideate`, `$devlyn:design-ui`); in Claude Code they're slash commands (`/devlyn:resolve`). Run it again anytime to update. (`npx devlyn-cli -y` installs the Claude core non-interactively; `npx devlyn-cli agents <cli>` adds one agent later.)
31
31
 
32
32
  ---
33
33
 
@@ -101,10 +101,10 @@ that, `/devlyn:resolve` stops with `BLOCKED:solo-ceiling-avoidance-required`.
101
101
 
102
102
  `--engine claude` (default) is the canonical implementation surface for PLAN, IMPLEMENT, BUILD_GATE, and CLEANUP. VERIFY/JUDGE conditionally runs pair mode for verify-only runs, high-risk specs, risk probes, mechanical warnings, coverage gaps, or explicit `--pair-verify`.
103
103
 
104
- `--engine codex` routes IMPLEMENT to Codex; `--engine auto` opts into the experimental dual-engine routing where applicable. Both are research-only at HEAD: iter-0020 closed Codex BUILD/IMPLEMENT below the quality floor on the 9-fixture suite (L2 vs L1 = −3.6, 3/8 gated fixtures cleared the +5 margin floor — release-readiness FAIL); iter-0033g + iter-0034 closed PLAN-pair as research-only with explicit unblock conditions (container/sandbox infra OR production telemetry capturing positive evidence of subagent introspection). Install the Codex CLI (https://platform.openai.com/docs/codex) and pass the flag explicitly to opt in:
104
+ `--engine codex` routes IMPLEMENT to Codex — research-only at HEAD: iter-0020 closed Codex BUILD/IMPLEMENT below the quality floor on the 9-fixture suite (L2 vs L1 = −3.6, 3/8 gated fixtures cleared the +5 margin floor — release-readiness FAIL); iter-0033g + iter-0034 closed PLAN-pair as research-only with explicit unblock conditions (container/sandbox infra OR production telemetry capturing positive evidence of subagent introspection). Install the Codex CLI (https://platform.openai.com/docs/codex) and pass the flag explicitly to opt in:
105
105
 
106
106
  ```
107
- /devlyn:resolve "fix the auth bug" --engine auto # experimental, research-only
107
+ /devlyn:resolve "fix the auth bug" --engine codex # research-only
108
108
  ```
109
109
 
110
110
  If Codex or Claude is absent when explicitly selected or conditionally required, the harness stops with `BLOCKED:codex-unavailable` or `BLOCKED:claude-unavailable` and prints setup guidance. Use `--no-pair` only when intentionally accepting solo VERIFY; use `--no-risk-probes` only when intentionally disabling automatic high-risk probes.
@@ -179,6 +179,7 @@ ids.
179
179
 
180
180
  ### Migration from earlier versions
181
181
 
182
+ <!-- legacy-surface-map:begin — retired command names below are documented as OLD, not current; lint Check 10c skips this block -->
182
183
  Earlier versions of devlyn-cli shipped 16+ slash commands. The iter-0034 Phase 4 cutover (2026-05-04) and the 2026-05-14 follow-up consolidated them down to the three current commands. Upgrades automatically purge the legacy skill directories from `~/.claude/skills/`.
183
184
 
184
185
  | Old command | Now use |
@@ -187,6 +188,7 @@ Earlier versions of devlyn-cli shipped 16+ slash commands. The iter-0034 Phase 4
187
188
  | `/devlyn:product-spec`, `/devlyn:feature-spec`, `/devlyn:recommend-features`, `/devlyn:discover-product` | `/devlyn:ideate` |
188
189
  | `/devlyn:team-design-ui` | `/devlyn:design-ui` (now always spawns the 5-specialist team — Creative Director, Product Designer, Visual Designer, Interaction Designer, Accessibility Designer) |
189
190
  | `/devlyn:design-system` | Removed 2026-05-14 — no replacement |
191
+ <!-- legacy-surface-map:end -->
190
192
 
191
193
  ---
192
194
 
@@ -215,7 +217,7 @@ Selected during install. Run `npx devlyn-cli` again to add more.
215
217
  | `asset-creator` | AI pixel art game asset pipeline — generate, chroma-key, catalog |
216
218
  | `cloudflare-nextjs-setup` | Cloudflare Workers + Next.js with OpenNext |
217
219
  | `generate-skill` | Create Claude Code skills following Anthropic best practices |
218
- | `prompt-engineering` | Claude 4 prompt optimization |
220
+ | `prompt-engineering` | Claude prompt optimization |
219
221
  | `better-auth-setup` | Better Auth + Hono + Drizzle + PostgreSQL |
220
222
  | `pyx-scan` | Check if an AI agent skill is safe before installing |
221
223
  | `dokkit` | Document template filling for DOCX/HWPX |
@@ -245,7 +247,7 @@ Selected during install. Run `npx devlyn-cli` again to add more.
245
247
  |---|---|
246
248
  | `playwright` | Playwright MCP — powers `/devlyn:resolve` BUILD_GATE browser tier (Chrome MCP → Playwright → curl fallback) |
247
249
 
248
- > `--engine auto/codex` and conditional VERIFY pair mode use the local `codex` CLI binary, not MCP. Install from https://platform.openai.com/docs/codex, run the current Codex auth/login flow, verify `codex --version`, then rerun.
250
+ > `--engine codex` and conditional VERIFY pair mode use the local `codex` CLI binary, not MCP. Install from https://platform.openai.com/docs/codex, run the current Codex auth/login flow, verify `codex --version`, then rerun.
249
251
 
250
252
  </details>
251
253
 
@@ -0,0 +1,8 @@
1
+ bench-test-repo의 CLI(bin/cli.js)에 있는 `hello` 서브커맨드에 `--loud` 플래그를 추가해주세요. `--loud`가 전달되면 인사말이 대문자로 바뀌고 느낌표 두 개로 끝나야 합니다.
2
+
3
+ 예시:
4
+ - `node bin/cli.js hello --loud` → `HELLO, WORLD!!`
5
+ - `node bin/cli.js hello --loud --name alice` → `HELLO, ALICE!!`
6
+ - `node bin/cli.js hello` → `Hello, world!` (기본값 변경 없음)
7
+
8
+ 기존 테스트가 계속 통과하는지 확인하고, `--loud` 경로에 대한 테스트를 최소 하나 추가해주세요. 관련 없는 파일은 건드리지 마세요 — `bin/cli.js`와 `tests/cli.test.js`만 수정하세요. 새로운 npm 의존성은 추가하지 마세요.
@@ -0,0 +1,28 @@
1
+ # F34 CLI settle in-flight
2
+
3
+ ## Failure mode
4
+
5
+ Detects implementations that couple authorization completion order to
6
+ settlement order. The two cheap-but-wrong shapes: (a) sequential
7
+ await-per-transfer — correct ledger, fails the visible overlap requirement
8
+ (`high_water >= 3`); (b) apply-as-authorizations-resolve — meets overlap,
9
+ corrupts file-order settlement (flips `t-60` accept and `t-40` reject in the
10
+ verifier input). The correct shape starts authorizations concurrently and
11
+ settles strictly in file order afterward — the decoupling is the measured
12
+ skill (Tier-1 "H1" from memory `project_pair_fixture_tier1_2026_05_05`;
13
+ keyword-hidden: spec never says race/lock/mutex/concurrency primitive).
14
+
15
+ ## Fairness
16
+
17
+ Every verifier assertion quotes a visible spec bullet via `contract_refs`
18
+ (lint-fixtures enforced). Gateway latency is deterministic (trailing digits
19
+ of the id = ms), high-water threshold is stated in the spec with the exact
20
+ sample size, and the flip case is derivable from the visible settlement rule
21
+ alone. No hidden shape or tie-break exists outside spec.md.
22
+
23
+ ## Headroom prediction (pre-registered, unmeasured)
24
+
25
+ bare ≤ 60 (expected to fail ordering-vs-completion or overlap), solo ≤ 80
26
+ (hypothesis: misses the decoupling — settles in completion order). To be
27
+ measured with `headroom-gate.py` before any pair-evidence claims; this
28
+ fixture carries no measurement claim until then.
@@ -0,0 +1,15 @@
1
+ # F34 retired
2
+
3
+ Retired from the active golden suite after headroom run
4
+ `iter-0039-headroom-f34-f35`.
5
+
6
+ Reason: bare scored 92, exceeding the headroom ceiling of 60, and
7
+ `solo_claude` scored 88, exceeding the headroom ceiling of 80. Both arms
8
+ solved the settle-inflight task competently (judge: bare 92 vs solo_claude 88,
9
+ margin -4) and share the same minor scope slip (touching
10
+ `data/gateway-stats.json` outside the expected diff); neither arm hit a hard
11
+ disqualifier. The task was not hard enough for bare, let alone solo — there is
12
+ no pair-lift headroom to measure.
13
+
14
+ Future use: rework the visible contract or hidden verifiers so the task
15
+ creates a fair pair-risk-probe gap without hiding requirements from the spec.
@@ -0,0 +1,57 @@
1
+ {
2
+ "verification_commands": [
3
+ {
4
+ "cmd": "node --test tests/cli.test.js",
5
+ "exit_code": 0,
6
+ "stdout_contains": [],
7
+ "stdout_not_contains": ["not ok "]
8
+ },
9
+ {
10
+ "cmd": "node \"$BENCH_FIXTURE_DIR/verifiers/ordered-settlement.js\"",
11
+ "exit_code": 0,
12
+ "stdout_contains": ["\"ok\":true"],
13
+ "stdout_not_contains": [],
14
+ "contract_refs": [
15
+ "Every transfer is authorized by calling `authorize(transfer)` from `lib/gateway.js` exactly once per transfer.",
16
+ "Regardless of the order in which authorizations complete, transfers settle in file order, each exactly once.",
17
+ "Each rejected transfer produces exact row shape `{ \"id\": string, \"error\": \"insufficient_funds\", \"available\": number, \"requested\": number }`, where `available` is the sender's balance at that transfer's turn in file order.",
18
+ "The seeded gateway records the maximum number of authorizations in flight at once in `data/gateway-stats.json`; settling the six-transfer sample in `## Verification` must record `high_water` of at least `3`.",
19
+ "The command must not modify `data/accounts.json`."
20
+ ]
21
+ },
22
+ {
23
+ "cmd": "node \"$BENCH_FIXTURE_DIR/verifiers/reject-invalid.js\"",
24
+ "exit_code": 0,
25
+ "stdout_contains": ["\"ok\":true"],
26
+ "stdout_not_contains": [],
27
+ "contract_refs": [
28
+ "Validation happens before any authorization starts.",
29
+ "unknown account, or non-positive or non-integer `amount` exits `2` and writes exactly one JSON error object to stderr with shape `{ \"error\": \"invalid_input\", \"reason\": string }`, printing nothing to stdout."
30
+ ]
31
+ }
32
+ ],
33
+ "forbidden_patterns": [
34
+ {
35
+ "pattern": "catch\\s*\\([^)]*\\)\\s*\\{[^}]*return\\s+(null|undefined|''|\\{\\})",
36
+ "description": "silent catch returning fallback in settle path",
37
+ "files": ["bin/cli.js"],
38
+ "severity": "disqualifier"
39
+ },
40
+ {
41
+ "pattern": "catch\\s*\\([^)]*\\)\\s*\\{\\s*\\}",
42
+ "description": "empty catch block",
43
+ "files": ["bin/cli.js"],
44
+ "severity": "disqualifier"
45
+ },
46
+ {
47
+ "pattern": "/\\*\\s*eslint-disable",
48
+ "description": "eslint-disable without scoped justification",
49
+ "files": ["bin/cli.js"],
50
+ "severity": "disqualifier"
51
+ }
52
+ ],
53
+ "required_files": ["bin/cli.js", "tests/cli.test.js", "data/accounts.json", "lib/gateway.js"],
54
+ "forbidden_files": [],
55
+ "max_deps_added": 0,
56
+ "spec_output_files": ["bin/cli.js", "tests/cli.test.js"]
57
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "id": "F34-cli-settle-inflight",
3
+ "category": "high-risk",
4
+ "difficulty": "high",
5
+ "timeout_seconds": 1500,
6
+ "required_tools": ["node"],
7
+ "browser": false,
8
+ "deps_change_expected": false,
9
+ "intent": "Add a bench-cli settle command that authorizes transfers through a seeded async gateway with overlapping in-flight authorizations while settling balances strictly in file order, with exact accepted/rejected/balances JSON output and validation before any authorization."
10
+ }
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env bash
2
+ # F34 setup — seed accounts and the concurrency-tracking gateway.
3
+ set -e
4
+
5
+ mkdir -p data lib
6
+
7
+ cat > data/accounts.json <<'JSON'
8
+ {
9
+ "acct-a": 100,
10
+ "acct-b": 0,
11
+ "acct-c": 50,
12
+ "acct-d": 10
13
+ }
14
+ JSON
15
+
16
+ cat > lib/gateway.js <<'JS'
17
+ 'use strict';
18
+ // Seeded authorization gateway. DO NOT MODIFY (fixture contract).
19
+ // Latency is deterministic per transfer id (trailing digits = milliseconds),
20
+ // so authorization completion order can differ from file order. Tracks how
21
+ // many authorizations are in flight at once and how many calls were made.
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+
25
+ const STATS_PATH = path.join(__dirname, '..', 'data', 'gateway-stats.json');
26
+ let inFlight = 0;
27
+ let highWater = 0;
28
+ let calls = 0;
29
+
30
+ function writeStats() {
31
+ fs.writeFileSync(STATS_PATH, JSON.stringify({ calls, high_water: highWater }) + '\n');
32
+ }
33
+
34
+ async function authorize(transfer) {
35
+ calls += 1;
36
+ inFlight += 1;
37
+ if (inFlight > highWater) highWater = inFlight;
38
+ writeStats();
39
+ const digits = String(transfer && transfer.id ? transfer.id : '').replace(/\D/g, '');
40
+ const ms = digits ? (Number(digits) % 100) || 5 : 5;
41
+ await new Promise((resolve) => setTimeout(resolve, ms));
42
+ inFlight -= 1;
43
+ writeStats();
44
+ return { authorized: true };
45
+ }
46
+
47
+ module.exports = { authorize };
48
+ JS
49
+
50
+ exit 0
@@ -0,0 +1,59 @@
1
+ ---
2
+ id: "F34-cli-settle-inflight"
3
+ title: "Settle command with in-flight authorizations"
4
+ status: planned
5
+ complexity: high
6
+ depends-on: []
7
+ ---
8
+
9
+ # F34 Settle command with in-flight authorizations
10
+
11
+ ## Context
12
+
13
+ `bench-cli` gets a `settle` command that previews a batch of credit
14
+ transfers between accounts. Every transfer must be authorized through the
15
+ seeded gateway module before settlement. The gateway is slow, so
16
+ authorizations for different transfers must be able to overlap — but the
17
+ ledger math must stay exact and order-stable.
18
+
19
+ ## Requirements
20
+
21
+ - [ ] `bench-cli settle --input <path>` reads JSON shaped as `{ "transfers": [{ "id": string, "from": string, "to": string, "amount": number }] }`.
22
+ - [ ] Account starting balances come from `data/accounts.json`. The command must not modify `data/accounts.json`.
23
+ - [ ] Every transfer is authorized by calling `authorize(transfer)` from `lib/gateway.js` exactly once per transfer. Do not modify, reimplement, or bypass `lib/gateway.js`.
24
+ - [ ] Authorizations must be able to overlap: while one transfer's authorization is awaited, later transfers' authorizations must be able to start. The seeded gateway records the maximum number of authorizations in flight at once in `data/gateway-stats.json`; settling the six-transfer sample in `## Verification` must record `high_water` of at least `3`.
25
+ - [ ] Regardless of the order in which authorizations complete, transfers settle in file order, each exactly once. A transfer is accepted when the sender's balance — after all earlier-in-file accepted transfers — covers the amount; otherwise it is rejected.
26
+ - [ ] Each rejected transfer produces exact row shape `{ "id": string, "error": "insufficient_funds", "available": number, "requested": number }`, where `available` is the sender's balance at that transfer's turn in file order.
27
+ - [ ] Validation happens before any authorization starts. Invalid JSON, missing or non-string `id`/`from`/`to`, unknown account, or non-positive or non-integer `amount` exits `2` and writes exactly one JSON error object to stderr with shape `{ "error": "invalid_input", "reason": string }`, printing nothing to stdout.
28
+ - [ ] On success, write exactly one JSON object to stdout and no stderr. Keys: `accepted` (array of transfer ids in file order), `rejected` (array of rejected rows in file order), `balances` (object with the final balance of every account in `data/accounts.json`).
29
+ - [ ] `tests/cli.test.js` is updated. Existing tests still pass AND at least two new tests cover `settle`: one where a later-in-file transfer depends on an earlier-in-file transfer's settlement, and one rejection.
30
+
31
+ ## Constraints
32
+
33
+ - **No new npm dependencies.**
34
+ - **No silent catches.** If parsing or file reading fails, emit a visible JSON error to stderr and exit `2`.
35
+ - **No extra stdout/stderr text** on the success path; downstream tooling parses stdout as JSON.
36
+ - Balances are integer credits; no floating-point money.
37
+
38
+ ## Out of Scope
39
+
40
+ - Persisting settlement results (settle is a preview; `data/accounts.json` stays untouched).
41
+ - Changing server routes, web UI, or `lib/gateway.js`.
42
+ - Retry, timeout, or failure handling for the gateway beyond what it returns.
43
+ - Touching `server/`, `web/`, or `tests/server.test.js`.
44
+
45
+ ## Verification
46
+
47
+ - `node --test tests/cli.test.js` exits 0.
48
+ - Settling a six-transfer file where authorizations complete in reverse file order still yields file-order settlement: exact `accepted`, `rejected`, and `balances` values.
49
+ - `data/gateway-stats.json` shows `calls` equal to the number of transfers and `high_water` of at least `3`.
50
+ - An unknown account exits `2`, prints one JSON error to stderr, and prints no stdout.
51
+ - `git diff --stat` shows only `bin/cli.js` and `tests/cli.test.js` touched (accounts and gateway are seeded by setup, not the arm).
52
+
53
+ ## Solo-headroom hypothesis
54
+
55
+ A capable solo_claude baseline is expected to miss the decoupling of
56
+ authorization completion order from file-order settlement — applying
57
+ balance changes as authorizations resolve instead of at each transfer's
58
+ file-order turn; observable command
59
+ `node "$BENCH_FIXTURE_DIR/verifiers/ordered-settlement.js"` exposes the miss.
@@ -0,0 +1,9 @@
1
+ Add a `settle` command to `bench-cli` so users can run `bench-cli settle --input <path>` with a transfers JSON file. Starting balances come from `data/accounts.json` (read-only — settle is a preview and must not modify it). Every transfer must be authorized by calling `authorize(transfer)` from the seeded `lib/gateway.js` exactly once; do not modify or bypass that module.
2
+
3
+ The gateway is slow, so authorizations for different transfers must be able to overlap — while one is awaited, later ones must be able to start. The seeded gateway records `calls` and the in-flight `high_water` mark in `data/gateway-stats.json`; a six-transfer batch must reach a high-water mark of at least 3.
4
+
5
+ No matter which order authorizations finish in, transfers settle in file order, each exactly once: a transfer is accepted when the sender's balance after all earlier-in-file accepted transfers covers the amount, otherwise it is rejected with the exact row `{ "id", "error": "insufficient_funds", "available", "requested" }` where `available` is the balance at that transfer's file-order turn.
6
+
7
+ Validate the whole file before any authorization starts: bad JSON, missing fields, unknown accounts, or non-positive/non-integer amounts exit `2` with exactly one JSON error object `{ "error": "invalid_input", "reason": string }` on stderr and nothing on stdout. On success print exactly one JSON object with `accepted` (ids in file order), `rejected` (rows in file order), and `balances` (every account) — and nothing on stderr.
8
+
9
+ Update `tests/cli.test.js`: existing tests still pass, plus at least two new settle tests (one where a later transfer depends on an earlier transfer's settlement, one rejection). No new npm dependencies. Only touch `bin/cli.js` and `tests/cli.test.js`.
@@ -0,0 +1,88 @@
1
+ 'use strict';
2
+ // F34 hidden verifier — file-order settlement under inverted completion order.
3
+ // Transfer ids encode gateway latency (trailing digits = ms), so the FIRST
4
+ // transfer in the file finishes authorization LAST. Applying balances in
5
+ // completion order (or rejecting against pre-batch balances) flips t-60 and
6
+ // t-40; only strict file-order settlement produces this exact output.
7
+
8
+ const fs = require('node:fs');
9
+ const os = require('node:os');
10
+ const path = require('node:path');
11
+ const { spawnSync } = require('node:child_process');
12
+
13
+ // Structural equality: object key order is NOT part of the visible contract;
14
+ // array order IS. Codex fairness-audit fix (stringify equality made JSON
15
+ // property order a hidden oracle narrower than spec.md).
16
+ function deepEqual(a, b) {
17
+ if (a === b) return true;
18
+ if (Array.isArray(a) && Array.isArray(b)) {
19
+ return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
20
+ }
21
+ if (a && b && typeof a === 'object' && typeof b === 'object' && !Array.isArray(a) && !Array.isArray(b)) {
22
+ const ka = Object.keys(a).sort();
23
+ const kb = Object.keys(b).sort();
24
+ return deepEqual(ka, kb) && ka.every((k) => deepEqual(a[k], b[k]));
25
+ }
26
+ return false;
27
+ }
28
+
29
+ const inputPath = path.join(os.tmpdir(), `settle-input-${process.pid}.json`);
30
+ fs.writeFileSync(inputPath, JSON.stringify({
31
+ transfers: [
32
+ { id: 't-80', from: 'acct-a', to: 'acct-b', amount: 70 },
33
+ { id: 't-60', from: 'acct-b', to: 'acct-c', amount: 50 },
34
+ { id: 't-40', from: 'acct-a', to: 'acct-d', amount: 40 },
35
+ { id: 't-20', from: 'acct-c', to: 'acct-a', amount: 100 },
36
+ { id: 't-10', from: 'acct-d', to: 'acct-b', amount: 5 },
37
+ { id: 't-5', from: 'acct-b', to: 'acct-a', amount: 25 }
38
+ ]
39
+ }));
40
+
41
+ const work = process.env.BENCH_WORKDIR;
42
+ const cli = path.join(work, 'bin', 'cli.js');
43
+ const result = spawnSync('node', [cli, 'settle', '--input', inputPath], {
44
+ cwd: work,
45
+ encoding: 'utf8'
46
+ });
47
+
48
+ let out;
49
+ try {
50
+ out = JSON.parse(result.stdout);
51
+ } catch {
52
+ out = null;
53
+ }
54
+
55
+ const expected = {
56
+ accepted: ['t-80', 't-60', 't-20', 't-10', 't-5'],
57
+ rejected: [
58
+ { id: 't-40', error: 'insufficient_funds', available: 30, requested: 40 }
59
+ ],
60
+ balances: { 'acct-a': 155, 'acct-b': 0, 'acct-c': 0, 'acct-d': 5 }
61
+ };
62
+
63
+ let stats;
64
+ try {
65
+ stats = JSON.parse(fs.readFileSync(path.join(work, 'data', 'gateway-stats.json'), 'utf8'));
66
+ } catch {
67
+ stats = null;
68
+ }
69
+
70
+ const accountsUntouched = (() => {
71
+ try {
72
+ const seeded = JSON.parse(fs.readFileSync(path.join(work, 'data', 'accounts.json'), 'utf8'));
73
+ return deepEqual(seeded, { 'acct-a': 100, 'acct-b': 0, 'acct-c': 50, 'acct-d': 10 });
74
+ } catch {
75
+ return false;
76
+ }
77
+ })();
78
+
79
+ const ok = result.status === 0
80
+ && result.stderr === ''
81
+ && deepEqual(out, expected)
82
+ && stats !== null
83
+ && stats.calls === 6
84
+ && stats.high_water >= 3
85
+ && accountsUntouched;
86
+
87
+ console.log(JSON.stringify({ ok, status: result.status, stderr: result.stderr, out, stats, accountsUntouched }));
88
+ process.exit(ok ? 0 : 1);