@navels/neal 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +206 -209
- package/SECURITY.md +16 -19
- package/dist/neal/adjudicator/{blocked-adjudicator.js → consultant.js} +21 -21
- package/dist/neal/adjudicator/execute.js +33 -33
- package/dist/neal/adjudicator/final-completion.js +2 -31
- package/dist/neal/adjudicator/planning.js +230 -38
- package/dist/neal/agents/prompts.js +6 -17
- package/dist/neal/agents/rounds.js +43 -73
- package/dist/neal/agents/schemas.js +47 -13
- package/dist/neal/blocked-guidance.js +16 -9
- package/dist/neal/cli.js +2 -2
- package/dist/neal/commands/check.js +2 -2
- package/dist/neal/commands/compat.js +100 -36
- package/dist/neal/commands/interactive-activity.js +2 -2
- package/dist/neal/commands/recovery-guidance.js +20 -8
- package/dist/neal/commands/runtime.js +4 -2
- package/dist/neal/config.js +16 -7
- package/dist/neal/context/inline-review-context.js +13 -28
- package/dist/neal/context/reviewer-context.js +19 -14
- package/dist/neal/eval/reviewer-eval-manifest.js +86 -0
- package/dist/neal/eval/reviewer-eval.js +155 -0
- package/dist/neal/interactive-controls.js +5 -0
- package/dist/neal/orchestrator/completion.js +49 -2
- package/dist/neal/orchestrator/failures.js +7 -0
- package/dist/neal/orchestrator/notifications.js +9 -9
- package/dist/neal/orchestrator/phases/planning.js +117 -80
- package/dist/neal/orchestrator/phases/recovery.js +151 -115
- package/dist/neal/orchestrator/phases/review.js +28 -1
- package/dist/neal/orchestrator/transitions.js +7 -1
- package/dist/neal/orchestrator.js +3 -0
- package/dist/neal/plan-queue.js +29 -1
- package/dist/neal/prompts/execute.js +7 -19
- package/dist/neal/prompts/planning.js +64 -55
- package/dist/neal/prompts/review-doctrine.js +13 -50
- package/dist/neal/prompts/specialized.js +7 -17
- package/dist/neal/prompts/specs.js +180 -32
- package/dist/neal/providers/agent-settings-isolation.js +35 -0
- package/dist/neal/providers/anthropic-claude.js +18 -1
- package/dist/neal/providers/detection.js +3 -7
- package/dist/neal/providers/git-config-isolation.js +83 -0
- package/dist/neal/providers/openai-codex.js +25 -3
- package/dist/neal/providers/{generic-agentic-tools.js → openai-compatible-tools.js} +11 -5
- package/dist/neal/providers/openai-compatible.js +1078 -430
- package/dist/neal/providers/openrouter-routing.js +68 -0
- package/dist/neal/providers/pricing.js +1 -1
- package/dist/neal/providers/rate-card.js +6 -32
- package/dist/neal/providers/registry.js +6 -17
- package/dist/neal/providers/types.js +12 -0
- package/dist/neal/resume-decision.js +7 -6
- package/dist/neal/resume-planner.js +9 -0
- package/dist/neal/retrospective.js +3 -3
- package/dist/neal/review-debt.js +30 -0
- package/dist/neal/review-findings/provider.js +17 -29
- package/dist/neal/review.js +17 -1
- package/dist/neal/state-invariants.js +11 -3
- package/dist/neal/state-views.js +27 -5
- package/dist/neal/state.js +59 -15
- package/dist/neal/status.js +28 -1
- package/dist/neal/support.js +3 -3
- package/docs/README.md +22 -18
- package/docs/{ADJUDICATOR_INVENTORY.md → adjudicator-inventory.md} +58 -57
- package/docs/architecture.md +79 -83
- package/docs/automation.md +11 -11
- package/docs/compat.md +91 -61
- package/docs/compatible-models.md +145 -121
- package/docs/demo.md +15 -5
- package/docs/maintenance.md +65 -34
- package/docs/plan-format.md +61 -33
- package/docs/prompt-evals.md +96 -0
- package/docs/{PROMPT_SPECS.md → prompt-specs.md} +52 -48
- package/docs/providers.md +279 -349
- package/docs/release.md +63 -81
- package/docs/review-convergence.md +246 -0
- package/docs/state-machine.md +61 -59
- package/docs/storage.md +40 -31
- package/docs/troubleshooting.md +32 -29
- package/examples/compat/add-edit-verify/PLAN.md +2 -0
- package/examples/compat/add-edit-verify/broken.diff +2 -2
- package/examples/compat/add-edit-verify/good.diff +2 -2
- package/examples/compat/add-edit-verify/src/add.js +1 -1
- package/examples/compat/manifest.json +25 -29
- package/examples/compat/sum-grep-edit/PLAN.md +33 -0
- package/examples/compat/sum-grep-edit/broken.diff +12 -0
- package/examples/compat/sum-grep-edit/good.diff +12 -0
- package/examples/compat/sum-grep-edit/package.json +5 -0
- package/examples/compat/sum-grep-edit/src/numbers.js +9 -0
- package/examples/compat/sum-grep-edit/test/numbers.test.js +15 -0
- package/examples/issue-triage-js/README.md +0 -15
- package/examples/reviewer-eval/clean-doc/base/src/clamp.ts +3 -0
- package/examples/reviewer-eval/clean-doc/change.diff +9 -0
- package/examples/reviewer-eval/clean-extract/base/src/greet.ts +3 -0
- package/examples/reviewer-eval/clean-extract/change.diff +13 -0
- package/examples/reviewer-eval/dropped-error/base/src/load.ts +4 -0
- package/examples/reviewer-eval/dropped-error/change.diff +15 -0
- package/examples/reviewer-eval/manifest.json +45 -0
- package/examples/reviewer-eval/off-by-one/base/src/paginate.ts +5 -0
- package/examples/reviewer-eval/off-by-one/change.diff +11 -0
- package/{config.yml → neal.yml} +1 -1
- package/package.json +15 -14
- package/dist/neal/providers/generic-agentic.js +0 -1256
- package/docs/comparison.md +0 -105
- package/docs/issue-pipeline.md +0 -124
- package/examples/compat/is-even-add-test/PLAN.md +0 -30
- package/examples/compat/is-even-add-test/broken.diff +0 -11
- package/examples/compat/is-even-add-test/good.diff +0 -11
- package/examples/compat/is-even-add-test/package.json +0 -5
- package/examples/compat/is-even-add-test/src/is-even.js +0 -3
- package/examples/compat/is-even-add-test/test/is-even.test.js +0 -9
- package/examples/compat/reverse-grep-edit/PLAN.md +0 -32
- package/examples/compat/reverse-grep-edit/broken.diff +0 -12
- package/examples/compat/reverse-grep-edit/good.diff +0 -12
- package/examples/compat/reverse-grep-edit/package.json +0 -5
- package/examples/compat/reverse-grep-edit/src/strings.js +0 -10
- package/examples/compat/reverse-grep-edit/test/strings.test.js +0 -15
package/docs/architecture.md
CHANGED
|
@@ -1,113 +1,109 @@
|
|
|
1
1
|
# Architecture
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
roles ◀──────┤ ├──────▶ persisted state
|
|
22
|
-
Provider │ │ .neal/ ledgers
|
|
23
|
-
registry ▼ ▼ (run + queue)
|
|
24
|
-
(planner / coder / reviewer) prompt specs + structured rounds
|
|
25
|
-
```
|
|
3
|
+
neal drives a plan document through a deterministic state machine with
|
|
4
|
+
planning and execution phases using coding agents in planner, coder, and reviewer roles.
|
|
5
|
+
The planning phase has the planner and reviewer revise the plan until
|
|
6
|
+
it has a valid execution shape, logical scopes, and a repository-grounded
|
|
7
|
+
implementation approach at a level a person can still review. File-level
|
|
8
|
+
discovery and local implementation choices stay in the execution phase.
|
|
9
|
+
|
|
10
|
+
During the execution phase, the coder implements a scope of work
|
|
11
|
+
with a fresh context, commits, and the reviewer checks the work. Any findings
|
|
12
|
+
are fed back to the coder and addressed. This coder/reviewer loop continues
|
|
13
|
+
until the reviewer is satisfied and execution proceeds to the next scope.
|
|
14
|
+
Once all scopes are complete, the coder and reviewer make a final pass to
|
|
15
|
+
review all the commits together. Finally, neal squashes all the work into a single commit.
|
|
16
|
+
|
|
17
|
+
Each role (planner, coder, reviewer) has its own prompt-driven behavior
|
|
18
|
+
and is backed by whatever provider (Anthropic, Codex, OpenRouter) and
|
|
19
|
+
model you choose. Run state is saved under `.neal/` so that a run can be
|
|
20
|
+
easily resumed without losing work in progress.
|
|
26
21
|
|
|
27
22
|
## 1. The run loop (orchestrator)
|
|
28
23
|
|
|
29
24
|
`src/neal/orchestrator.ts` drives a deterministic state machine over a plan
|
|
30
25
|
document. The phases live in `src/neal/orchestrator/phases/`:
|
|
31
26
|
|
|
32
|
-
- **planning
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
- **planning:** uses its own planner/reviewer loop to refine the plan you wrote
|
|
28
|
+
into a human-reviewable shape neal can execute,
|
|
29
|
+
decides whether it is `one_shot`, `multi_scope`, or `multi_scope_unknown`,
|
|
30
|
+
and records that choice in the plan (`plan-refinement.ts` and
|
|
31
|
+
`plan-validation.ts`). See [plan-format.md](plan-format.md).
|
|
32
|
+
- **coder:** runs one scope with the coder role from a fresh context, then
|
|
36
33
|
commits.
|
|
37
|
-
- **review
|
|
38
|
-
|
|
39
|
-
- **recovery
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
34
|
+
- **review:** hands the committed diff to the read-only reviewer. Its findings
|
|
35
|
+
go back to the coder until the reviewer accepts the change.
|
|
36
|
+
- **recovery:** when a run gets stuck, the consultant is engaged. This is a role
|
|
37
|
+
backed by the reviewer provider/model (`adjudicator/consultant.ts`):
|
|
38
|
+
it can inspect the run but never change it, and it gets only a limited number
|
|
39
|
+
of tries. There are three kinds of stuck:
|
|
40
|
+
the coder reports it can't proceed, the coder and reviewer keep going back and
|
|
41
|
+
forth without resolution, or a step that tried to break a
|
|
42
|
+
scope into a smaller plan produced a plan that wasn't valid. When the fix is
|
|
43
|
+
small and safe, the consultant returns a directive that neal applies, and the
|
|
44
|
+
run keeps going. Otherwise the run stops and waits for you, or fails cleanly
|
|
45
|
+
if it's running unattended.
|
|
46
|
+
|
|
47
|
+
After every scope is accepted, a final-completion review
|
|
48
48
|
(`final-completion-review.ts`) checks the whole plan, then neal squashes the run
|
|
49
|
-
into a clean commit
|
|
49
|
+
into a clean commit.
|
|
50
50
|
|
|
51
51
|
## 2. State and persistence
|
|
52
52
|
|
|
53
|
-
Every run is
|
|
54
|
-
|
|
55
|
-
(`atomic-write.ts`). Two
|
|
53
|
+
Every run is crash-safe and resumable, because all state is saved under the
|
|
54
|
+
project-local `.neal/` directory ([storage.md](storage.md)) with atomic writes
|
|
55
|
+
(`atomic-write.ts`). Two files hold that state:
|
|
56
56
|
|
|
57
|
-
- **Run state** (`RUN_STATE.json`)
|
|
58
|
-
|
|
59
|
-
- **Plan queue** (`plan-queue.ts`)
|
|
60
|
-
|
|
57
|
+
- **Run state** (`RUN_STATE.json`): the phase and status of a single run, with
|
|
58
|
+
its invariants documented in [state-machine.md](state-machine.md).
|
|
59
|
+
- **Plan queue** (`plan-queue.ts`): the ordered list of scopes and which ones
|
|
60
|
+
are done.
|
|
61
61
|
|
|
62
|
-
`neal resume` (`resume-decision.ts`, `resume-planner.ts`)
|
|
63
|
-
|
|
64
|
-
committed work. A per-run lock (`run-lock.ts`) prevents concurrent runs from
|
|
65
|
-
colliding.
|
|
62
|
+
`neal resume` (`resume-decision.ts`, `resume-planner.ts`) rebuilds an interrupted
|
|
63
|
+
run from these files. A per-run lock (`run-lock.ts`) keeps two runs from colliding.
|
|
66
64
|
|
|
67
65
|
## 3. Providers and roles
|
|
68
66
|
|
|
69
|
-
The three roles
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
declared and
|
|
67
|
+
The three roles (planner, coder, reviewer) are each bound to a provider on their
|
|
68
|
+
own, so you can run a different vendor or model for each one. The provider
|
|
69
|
+
registry (`src/neal/providers/registry.ts`) is where each provider's capabilities
|
|
70
|
+
are declared and enforced:
|
|
73
71
|
|
|
74
72
|
- **Native adapters:** `openai-codex` and `anthropic-claude` wrap the vendors'
|
|
75
|
-
agentic SDKs
|
|
76
|
-
- **`
|
|
77
|
-
OpenRouter model
|
|
78
|
-
(`providers/
|
|
79
|
-
|
|
80
|
-
The
|
|
81
|
-
provider
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
73
|
+
agentic SDKs, which own their own tool loop and sandbox.
|
|
74
|
+
- **`openai-compatible`:** a neal-owned agent loop over any OpenAI-compatible or
|
|
75
|
+
OpenRouter model. File tools are jailed to the repository, while the coder's
|
|
76
|
+
shell tool is unsandboxed (`providers/openai-compatible-tools.ts`).
|
|
77
|
+
|
|
78
|
+
The most important rule: the reviewer can't write, by construction. At
|
|
79
|
+
registration, every provider is checked (`assertStructuredAdvisorReadOnly`) to
|
|
80
|
+
confirm its reviewer capability declares `write: false` and `shell: false`. So a
|
|
81
|
+
reviewer can't change the repo, no matter what the prompt says. See
|
|
82
|
+
[providers.md](providers.md) and [SECURITY.md](../SECURITY.md) for the full trust
|
|
83
|
+
model.
|
|
86
84
|
|
|
87
85
|
## 4. Prompts and adjudication
|
|
88
86
|
|
|
89
|
-
Each role's behavior
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
(`agents/schemas.ts`). The coder/reviewer
|
|
93
|
-
[
|
|
94
|
-
schema-enforced (not loose JSON), which is also how `neal compat` attributes a
|
|
95
|
-
model's failures to the model rather than to transport noise.
|
|
87
|
+
Each role's behavior comes from versioned prompt specs (`src/neal/prompts/`,
|
|
88
|
+
[prompt-specs.md](prompt-specs.md)). They run through shared structured rounds
|
|
89
|
+
(`agents/rounds.ts`) that force the model's output to match a schema
|
|
90
|
+
(`agents/schemas.ts`). The full coder/reviewer loop is listed in
|
|
91
|
+
[adjudicator-inventory.md](adjudicator-inventory.md).
|
|
96
92
|
|
|
97
93
|
## 5. Model qualification (`neal compat`)
|
|
98
94
|
|
|
99
|
-
`neal compat` (`src/neal/commands/compat.ts`) is a self-contained harness
|
|
100
|
-
|
|
101
|
-
bundled fixtures,
|
|
102
|
-
|
|
103
|
-
[compatible-models.md](compatible-models.md) whitelist
|
|
104
|
-
|
|
95
|
+
`neal compat` (`src/neal/commands/compat.ts`) is a self-contained harness. It
|
|
96
|
+
runs a candidate model through the planner, coder, and reviewer roles on small
|
|
97
|
+
bundled fixtures, sorts any failures into clear kinds (protocol,
|
|
98
|
+
structured-output, or behavior), and prints a PASS/FAIL table. It's how the
|
|
99
|
+
[compatible-models.md](compatible-models.md) whitelist gets made, and it also
|
|
100
|
+
serves as the live CI smoke test when a dependency is bumped (see
|
|
105
101
|
[maintenance.md](maintenance.md) and [compat.md](compat.md)).
|
|
106
102
|
|
|
107
103
|
## Where to go next
|
|
108
104
|
|
|
109
|
-
- [plan-format.md](plan-format.md)
|
|
110
|
-
- [state-machine.md](state-machine.md)
|
|
111
|
-
- [providers.md](providers.md)
|
|
112
|
-
- [
|
|
113
|
-
- [storage.md](storage.md)
|
|
105
|
+
- [plan-format.md](plan-format.md): the executable plan contract
|
|
106
|
+
- [state-machine.md](state-machine.md): run/queue state invariants
|
|
107
|
+
- [providers.md](providers.md): provider adapter contract + extension checklist
|
|
108
|
+
- [prompt-specs.md](prompt-specs.md): prompt-spec ownership boundaries
|
|
109
|
+
- [storage.md](storage.md): the `.neal/` storage contract
|
package/docs/automation.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
# Automation
|
|
1
|
+
# Automation contract
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
The machine-facing contract for driving neal from scripts, CI, and benchmark
|
|
4
|
+
harnesses.
|
|
5
5
|
|
|
6
6
|
Noninteractive harnesses should run `neal execute <plan.md>` for an already
|
|
7
|
-
accepted executable plan, or `neal run <plan.md>` when
|
|
7
|
+
accepted executable plan, or `neal run <plan.md>` when neal should refine and
|
|
8
8
|
execute one or more plans as a serial queue. Use `neal resume --run <run-id>` to
|
|
9
|
-
continue an interrupted writer run
|
|
9
|
+
continue an interrupted writer run. Pass `--message` only when status says the
|
|
10
10
|
run is waiting for operator guidance. After each writer command, classify the
|
|
11
11
|
result from the process exit code plus `neal status --json --run <run-id>` when
|
|
12
12
|
a run id is known. Use `neal status --json --all` only for run discovery and
|
|
@@ -32,15 +32,15 @@ The stable JSON classification fields are:
|
|
|
32
32
|
- `commits`, `squash`, and `patch` for patch selection. `patch` includes the
|
|
33
33
|
default-submission decision, reason, base/head/range, source, commit count,
|
|
34
34
|
changed-file count, changed files, and unavailable reason.
|
|
35
|
-
- `build` for reproducibility:
|
|
35
|
+
- `build` for reproducibility: neal package version, neal source Git SHA when
|
|
36
36
|
available, Node version, whether the values came from run `meta.json` or live
|
|
37
37
|
fallback, and the persisted planner/coder/reviewer agent config.
|
|
38
38
|
|
|
39
39
|
Default public patch submission is conservative. Submit automatically only when
|
|
40
|
-
`patch.defaultSubmissionEligible` is true.
|
|
40
|
+
`patch.defaultSubmissionEligible` is true. neal sets that only for clean
|
|
41
41
|
completed execute runs with a non-empty readable patch range. Successful squash
|
|
42
|
-
metadata is preferred (`squash.originalBaseCommit..squash.replacementCommit`)
|
|
43
|
-
|
|
42
|
+
metadata is preferred (`squash.originalBaseCommit..squash.replacementCommit`).
|
|
43
|
+
Otherwise completed unsquashed execute runs use
|
|
44
44
|
`commits.initialBaseCommit ?? commits.baseCommit` through `commits.finalCommit`.
|
|
45
45
|
Failed, blocked, paused, running, provider-error, waiting, timed-out,
|
|
46
46
|
malformed-squash, pending-squash, unreadable-range, and empty-patch runs can
|
|
@@ -54,9 +54,9 @@ run-local `RUN_NARRATIVE.md` path reported at
|
|
|
54
54
|
diffs, prompts, provider responses, or provider payloads as benchmark traces.
|
|
55
55
|
|
|
56
56
|
Writer command exit codes (`0`, `1`, `2`, `3`) are defined in the README's
|
|
57
|
-
[Command
|
|
57
|
+
[Command exit codes](../README.md#command-exit-codes) section.
|
|
58
58
|
|
|
59
|
-
Harnesses own wall-clock timeouts. Launch
|
|
59
|
+
Harnesses own wall-clock timeouts. Launch neal in a process group, terminate
|
|
60
60
|
that group on timeout, wait a short grace period, and force-kill the group if it
|
|
61
61
|
does not exit. When a run id is known, call `neal status --json --run <run-id>`
|
|
62
62
|
after termination and record the wrapper timeout as the primary classification.
|
package/docs/compat.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
# `neal compat
|
|
1
|
+
# `neal compat`: model compatibility smoke test
|
|
2
2
|
|
|
3
3
|
`neal compat` answers one question per model: **can this OpenAI-compatible model
|
|
4
|
-
drive neal's loop at all
|
|
4
|
+
drive neal's loop at all**, as a coder, a reviewer, or a planner? It is a
|
|
5
5
|
**compatibility smoke test, not a performance benchmark**. There is no
|
|
6
6
|
resolve-rate, no score, no ranking, and no real SWE-bench repo involved. The
|
|
7
|
-
fixtures are deliberately trivial
|
|
7
|
+
fixtures are deliberately trivial. Any working agent should pass them. A failure
|
|
8
8
|
therefore means something is **fundamentally wrong** with the model's ability to
|
|
9
|
-
operate neal's contract
|
|
9
|
+
operate neal's contract. It does not mean a task was hard.
|
|
10
10
|
|
|
11
11
|
`neal compat` is fully self-contained in the neal repo. The only things an
|
|
12
|
-
operator needs to qualify a model are the neal checkout plus an API key
|
|
12
|
+
operator needs to qualify a model are the neal checkout plus an API key: no
|
|
13
13
|
external benchmark assets, no dataset download, no remote runner.
|
|
14
14
|
|
|
15
15
|
## Compatibility, not skill
|
|
@@ -18,7 +18,7 @@ external benchmark assets, no dataset download, no remote runner.
|
|
|
18
18
|
tool calls + structured output, no max-step loops, no unresolved operator
|
|
19
19
|
blocks, a clean finalization) **and** solve a trivial bundled fixture.
|
|
20
20
|
- **Not measured:** skill. No resolve-rate, no scores, no rankings, no real
|
|
21
|
-
SWE-bench repos
|
|
21
|
+
SWE-bench repos, trivial fixtures only.
|
|
22
22
|
- **Outcome:** a binary **PASS / FAIL per (model, role)**, with a structural
|
|
23
23
|
failure mode recorded for every FAIL.
|
|
24
24
|
|
|
@@ -30,42 +30,42 @@ external benchmark assets, no dataset download, no remote runner.
|
|
|
30
30
|
role's provider can answer one small structured probe
|
|
31
31
|
(`provider_check_payload`). It proves *connectivity and basic protocol*, not
|
|
32
32
|
that the model can complete a full neal run. When the resolved **coder or
|
|
33
|
-
reviewer** role uses
|
|
34
|
-
provider, `neal check` prints a one-line pointer: *"This is
|
|
33
|
+
reviewer** role uses the non-native `openai-compatible`
|
|
34
|
+
provider, `neal check` prints a one-line pointer: *"This is an openai-compatible
|
|
35
35
|
model - run `neal compat` to confirm it can drive the full loop."* Native
|
|
36
36
|
adapters (`openai-codex`, `anthropic-claude`) do not get the pointer. The
|
|
37
37
|
planner inherits the coder provider by default, so the coder check covers the
|
|
38
|
-
common case
|
|
38
|
+
common case. An explicitly configured non-native planner paired with native
|
|
39
39
|
coder/reviewer is not separately flagged (run `neal compat --role planner`
|
|
40
40
|
to qualify it).
|
|
41
41
|
- **`neal compat`** runs the same structured pre-filter first (it reuses
|
|
42
42
|
`check`'s `verifyConfiguredProviders`), then drives the model through complete
|
|
43
43
|
neal runs against the bundled fixtures and produces a PASS/FAIL matrix.
|
|
44
44
|
|
|
45
|
-
So
|
|
46
|
-
qualification gate that `check` points
|
|
45
|
+
So `neal check` is the cheap connectivity gate. `neal compat` is the full-loop
|
|
46
|
+
qualification gate that `check` points openai-compatible users toward.
|
|
47
47
|
|
|
48
48
|
## Usage
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
neal compat [--model <slug>] [--role coder|reviewer|planner|all] [--reference openai-codex|anthropic-claude|
|
|
51
|
+
neal compat [--model <slug>] [--role coder|reviewer|planner|all] [--reference openai-codex|anthropic-claude|openai-compatible:<slug>] [--json]
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
- `--role` (default `all`) selects which role(s) to test: `coder`, `reviewer`,
|
|
55
55
|
`planner`, or `all`.
|
|
56
|
-
- `--model <slug>` runs the slug on the **`
|
|
57
|
-
candidate role (provider forced to `
|
|
56
|
+
- `--model <slug>` runs the slug on the **`openai-compatible`** provider in the
|
|
57
|
+
candidate role (provider forced to `openai-compatible`, any configured effort
|
|
58
58
|
dropped, so the slug drives a clean OpenRouter call). When omitted, the
|
|
59
59
|
candidate role uses its configured provider/model unchanged.
|
|
60
60
|
- `--reference <id>` names the provider for the **non-candidate** roles so a
|
|
61
61
|
FAIL is attributable to the candidate in the tested role, not to a weak (and
|
|
62
|
-
possibly flaky) partner. Accepted forms: a native provider id
|
|
63
|
-
|
|
64
|
-
`
|
|
62
|
+
possibly flaky) partner. Accepted forms: a native provider id (`openai-codex`,
|
|
63
|
+
the default when omitted while `--model` is set, or `anthropic-claude`), or
|
|
64
|
+
`openai-compatible:<openrouter-model>` to run the reference roles on an
|
|
65
65
|
OpenRouter model. A bare model slug is rejected. Native reference roles run on
|
|
66
66
|
that adapter's default model (`gpt-5.5` for `openai-codex`, `claude-opus-4-8`
|
|
67
67
|
for `anthropic-claude`). Prefer a native reference for whitelist
|
|
68
|
-
qualification
|
|
68
|
+
qualification. See [compatible-models.md](compatible-models.md) for why
|
|
69
69
|
OpenRouter references proved unreliable as qualification partners. When
|
|
70
70
|
**neither** `--model` nor `--reference` is given, every role stays on its
|
|
71
71
|
configured provider/model (pure pass-through).
|
|
@@ -74,8 +74,10 @@ neal compat [--model <slug>] [--role coder|reviewer|planner|all] [--reference op
|
|
|
74
74
|
|
|
75
75
|
The command resolves its base config via the same writer-provider resolver the
|
|
76
76
|
other commands use, then derives a candidate config by cloning it and routing the
|
|
77
|
-
tested role onto `
|
|
78
|
-
roles onto the
|
|
77
|
+
tested role onto `openai-compatible` (when `--model` is set) and the non-candidate
|
|
78
|
+
roles onto the resolved reference provider: a native adapter at its default
|
|
79
|
+
model, or `openai-compatible` at the slug from the
|
|
80
|
+
`openai-compatible:<slug>` form (when either flag is set). It runs
|
|
79
81
|
against **throwaway git copies** of the bundled fixtures and **never mutates** a
|
|
80
82
|
committed fixture under `examples/compat/`. The process exits non-zero when the
|
|
81
83
|
overall result is FAIL.
|
|
@@ -95,18 +97,29 @@ fixtures are:
|
|
|
95
97
|
| Fixture | Roles | Contract surface |
|
|
96
98
|
| --- | --- | --- |
|
|
97
99
|
| `add-edit-verify` | coder, reviewer | edit + verify + commit |
|
|
98
|
-
| `
|
|
99
|
-
| `is-even-add-test` | coder, reviewer | add-a-test |
|
|
100
|
+
| `sum-grep-edit` | coder, reviewer | read/grep-then-edit |
|
|
100
101
|
| `plan-greeting` | planner | emit a `one_shot` plan |
|
|
101
102
|
|
|
103
|
+
Each fixture maps to a distinct way neal *directs* a model. A fixture that only
|
|
104
|
+
varies the coding task (not the direction shape) would measure skill, which this
|
|
105
|
+
command deliberately does not. That is why there are exactly three.
|
|
106
|
+
|
|
102
107
|
Each coder fixture bundles buggy source, a test that fails against the buggy
|
|
103
108
|
source, and a `PLAN.md` declaring `executionShape: one_shot` that describes the
|
|
104
109
|
smallest complete fix. The reviewer fixtures reuse the same projects, each paired
|
|
105
110
|
with two pre-made unified diffs (`good.diff`, `broken.diff`). The planner fixture
|
|
106
111
|
provides an issue-statement seed (`ISSUE.md`) and no reviewer diffs.
|
|
107
112
|
|
|
113
|
+
Fixture source and test files deliberately avoid any comment or claim that
|
|
114
|
+
becomes false once `good.diff` applies (`// BUG: …`, "intentionally buggy",
|
|
115
|
+
a stale `// TODO:`). A reviewer worktree is the project plus the applied diff, so
|
|
116
|
+
such text is a real defect in the material under review and draws legitimate
|
|
117
|
+
blocking findings on the known-good diff. The one exception is each fixture's
|
|
118
|
+
`PLAN.md`, which describes the pre-fix state by definition and carries a fixture
|
|
119
|
+
note saying so.
|
|
120
|
+
|
|
108
121
|
`examples/issue-triage-js` remains an optional tier-2 "real-ish" check above the
|
|
109
|
-
trivial gate
|
|
122
|
+
trivial gate. `neal compat` does not require it.
|
|
110
123
|
|
|
111
124
|
### Manifest schema
|
|
112
125
|
|
|
@@ -132,27 +145,33 @@ trivial gate; `neal compat` does not require it.
|
|
|
132
145
|
A planner fixture sets `"roles": ["planner"]` and provides `"issuePrompt"`
|
|
133
146
|
instead of `reviewer`. Any fixture carrying the `reviewer` role must also define
|
|
134
147
|
`verifyCommand`: reviewer fixtures reuse the coder project's command as the
|
|
135
|
-
deterministic good/broken oracle
|
|
148
|
+
deterministic good/broken oracle: `goodDiff` must make it pass, `brokenDiff`
|
|
136
149
|
must make it fail. The `referenceFix` is used only by the structural fixture
|
|
137
150
|
self-test (`test/compat-fixtures.test.ts`), never by a model run.
|
|
138
151
|
|
|
139
152
|
## Definition of PASS (per model, per role)
|
|
140
153
|
|
|
141
154
|
Across **every** fixture for the role, the model must (1) **complete the run
|
|
142
|
-
cleanly**
|
|
143
|
-
not
|
|
144
|
-
|
|
155
|
+
cleanly** and (2) produce the **role-correct result**. A clean run reaches
|
|
156
|
+
`status: 'done'` (not `'failed'` / `'blocked'`), does not emit an
|
|
157
|
+
`unattended.block_unresolved` event, and does not exceed its step/round budget.
|
|
158
|
+
The role-correct result is:
|
|
145
159
|
|
|
146
160
|
- **coder:** `finalState.status === 'done'` **and** the fixture's
|
|
147
161
|
`verifyCommand` exits `0` in the throwaway worktree (the failing test now
|
|
148
162
|
passes).
|
|
149
163
|
- **reviewer:** the review-findings loop converges (`outcome === 'accepted'`) for
|
|
150
|
-
**both** diffs, and the converged findings discriminate them
|
|
151
|
-
findings
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
164
|
+
**both** diffs, and the converged findings **discriminate** them. Counting
|
|
165
|
+
blocking findings (a `ReviewFindingItem` with `severity === 'blocking'`) per
|
|
166
|
+
diff, PASS iff `blocking(broken) >= 1` **and** `blocking(good) < blocking(broken)`.
|
|
167
|
+
Severity calibration is **not** graded: a reviewer may raise blocking findings
|
|
168
|
+
on the good diff and still PASS, as long as the broken diff draws strictly more.
|
|
169
|
+
A reviewer that blocks on neither diff, or equally on both, FAILs. `outcome`
|
|
170
|
+
alone is not sufficient (it is `'accepted'` whenever the findings artifact
|
|
171
|
+
converges, including a zero-finding artifact), so the verdict is scored on
|
|
172
|
+
`draft.findings` severities. The verdict is a **pair** verdict: both the good
|
|
173
|
+
and broken cells carry the same `pass`, and each carries its own
|
|
174
|
+
`blockingCount`.
|
|
156
175
|
- **planner** (secondary): the emitted plan document validates via
|
|
157
176
|
`validatePlanDocument` as a schema-conformant `one_shot` plan.
|
|
158
177
|
|
|
@@ -164,44 +183,48 @@ penalized for a halt that a human operator would simply have resumed.
|
|
|
164
183
|
|
|
165
184
|
## Failure-mode taxonomy
|
|
166
185
|
|
|
167
|
-
Each FAIL records a mode derived from **structural run state**
|
|
168
|
-
emitted run events, `ReviewFindingsOutcome`, and
|
|
169
|
-
from substring-matching model prose. When more than
|
|
170
|
-
this list (most specific cause first) is recorded:
|
|
186
|
+
Each FAIL records a mode derived from **structural run state**
|
|
187
|
+
(`finalState.status`, emitted run events, `ReviewFindingsOutcome`, and
|
|
188
|
+
`validatePlanDocument`), never from substring-matching model prose. When more than
|
|
189
|
+
one applies, the earliest in this list (most specific cause first) is recorded:
|
|
171
190
|
|
|
172
|
-
- `protocol
|
|
173
|
-
(it could not emit one valid `provider_check_payload`)
|
|
174
|
-
- `provider_failed
|
|
191
|
+
- `protocol`: the `verifyConfiguredProviders` pre-filter threw for the candidate
|
|
192
|
+
(it could not emit one valid `provider_check_payload`). Fixtures were skipped.
|
|
193
|
+
- `provider_failed`: the run failed with a model-attributable provider error
|
|
175
194
|
event (transport/auth/transient/other) rather than a clean completion, **except**
|
|
176
195
|
a provider error whose `errorKind` is a structured-output kind, which is recorded
|
|
177
196
|
as `structured_output` (below). This is also the bucket for a **writer** run that
|
|
178
|
-
ends `status: 'failed'` without a more specific structural signal
|
|
197
|
+
ends `status: 'failed'` without a more specific structural signal, including
|
|
179
198
|
step/round-budget exhaustion, which the current runtime does not surface to compat
|
|
180
199
|
as a distinct cap event, so writer step-cap exhaustion is reported here rather than
|
|
181
200
|
as `max_step_loop`.
|
|
182
|
-
- `block_unresolved
|
|
183
|
-
escalated to an operator block unattended mode could not resolve within
|
|
184
|
-
or the review loop's outcome was `'blocked'`.
|
|
185
|
-
- `max_step_loop
|
|
201
|
+
- `block_unresolved`: the run emitted `unattended.block_unresolved` (the model
|
|
202
|
+
escalated to an operator block that unattended mode could not resolve within
|
|
203
|
+
budget), or the review loop's outcome was `'blocked'`.
|
|
204
|
+
- `max_step_loop`: the **reviewer** loop's outcome was `'cap_reached'` (the
|
|
186
205
|
review-findings convergence cap was hit). Writer (coder/planner) step-cap
|
|
187
206
|
exhaustion is not separately distinguishable under the current runtime and is
|
|
188
207
|
classified as `provider_failed` (above).
|
|
189
|
-
- `wrong_or_empty_output
|
|
190
|
-
exited non-zero (or the diff was empty); reviewer: the
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
208
|
+
- `wrong_or_empty_output`. Coder: the run reached `done` but `verifyCommand`
|
|
209
|
+
exited non-zero (or the diff was empty); reviewer: the findings did not
|
|
210
|
+
discriminate the pair (no blocking finding on the broken diff, or the good diff
|
|
211
|
+
drew at least as many blocking findings as the broken one), recorded on both
|
|
212
|
+
cells of the pair; planner: the emitted plan was not a schema-conformant
|
|
213
|
+
`one_shot` plan. When one diff's review **failed** outright, the pair verdict is
|
|
214
|
+
unscoreable: that diff keeps its own failure mode and the other cell fails with
|
|
215
|
+
the same mode and a `pair unscoreable: …` detail, so the systematic cause is
|
|
216
|
+
what gets attributed.
|
|
217
|
+
- `structured_output`: the model could not produce or honor **schema-enforced**
|
|
195
218
|
JSON. This covers a reviewer/planner round whose payload failed schema validation
|
|
196
219
|
(surfaced as the corresponding round error) **and** a coder/reviewer provider
|
|
197
220
|
error whose `errorKind` is `structured_output_invalid` or
|
|
198
|
-
`structured_output_missing
|
|
221
|
+
`structured_output_missing`, e.g. an OpenRouter (openai-compatible) gateway that
|
|
199
222
|
rejects the `type: 'json_schema'` request (HTTP 400) or returns a missing/invalid
|
|
200
223
|
object. This is distinct from `provider_failed` (transport/auth/other transport
|
|
201
224
|
errors) and from `wrong_or_empty_output` (a syntactically valid verdict/output
|
|
202
225
|
that was substantively wrong). Both the coder (`classifyWriterFailure`) and
|
|
203
226
|
reviewer (`classifyReviewerThrownFailure`) paths attribute these kinds here.
|
|
204
|
-
- `finalization_error
|
|
227
|
+
- `finalization_error`: the run failed during finalization/artifact writing, or
|
|
205
228
|
the run produced no final document where one was expected.
|
|
206
229
|
|
|
207
230
|
When no structural signal is conclusive, `provider_failed` is recorded with the
|
|
@@ -220,20 +243,21 @@ whitelist (`docs/compatible-models.md`):
|
|
|
220
243
|
|
|
221
244
|
```jsonc
|
|
222
245
|
{
|
|
223
|
-
"schemaVersion":
|
|
246
|
+
"schemaVersion": 2,
|
|
224
247
|
"model": "deepseek/deepseek-chat", // candidate --model, or null when omitted
|
|
225
|
-
"reference": null, // native provider id (openai-codex | anthropic-claude), or null when omitted
|
|
248
|
+
"reference": null, // the --reference value as given: a native provider id (openai-codex | anthropic-claude), openai-compatible:<slug>, or null when omitted
|
|
226
249
|
"role": "all", // coder | reviewer | planner | all
|
|
227
|
-
"candidateProviders": { // provider each role used as the candidate: "
|
|
228
|
-
"coder": "
|
|
229
|
-
"reviewer": "
|
|
230
|
-
"planner": "
|
|
250
|
+
"candidateProviders": { // provider each role used as the candidate: "openai-compatible" per role when --model is set, else the configured provider
|
|
251
|
+
"coder": "openai-compatible",
|
|
252
|
+
"reviewer": "openai-compatible",
|
|
253
|
+
"planner": "openai-compatible"
|
|
231
254
|
},
|
|
232
255
|
"cells": [
|
|
233
256
|
{
|
|
234
257
|
"role": "coder", // coder | reviewer | planner
|
|
235
258
|
"fixtureId": "add-edit-verify", // manifest id; "provider:<role>" for a protocol pre-filter FAIL
|
|
236
259
|
"diffKind": null, // "good" | "broken" for reviewer cells, else null
|
|
260
|
+
"blockingCount": null, // blocking findings on this diff; null for non-reviewer or errored cells
|
|
237
261
|
"pass": true,
|
|
238
262
|
"failureMode": null, // one of the taxonomy modes when pass=false, else null
|
|
239
263
|
"detail": null // human-readable explanation when pass=false, else null
|
|
@@ -251,6 +275,12 @@ whitelist (`docs/compatible-models.md`):
|
|
|
251
275
|
}
|
|
252
276
|
```
|
|
253
277
|
|
|
278
|
+
No tooling gates on `schemaVersion`. It exists so a stored report's scoring
|
|
279
|
+
semantics are identifiable. Version `2` is the discrimination criterion described
|
|
280
|
+
above plus `blockingCount`. Version `1` reports scored the reviewer on zero
|
|
281
|
+
blocking findings for the good diff and retained no counts, so they cannot be
|
|
282
|
+
re-scored.
|
|
283
|
+
|
|
254
284
|
A role roll-up is PASS iff it has at least one cell and every one of its cells
|
|
255
285
|
passed. `overallPass` is true iff every targeted role roll-up passed. The process
|
|
256
286
|
exits non-zero when `overallPass` is false.
|
|
@@ -260,10 +290,10 @@ exits non-zero when `overallPass` is false.
|
|
|
260
290
|
Cheap by construction: trivial fixtures and cheap models. If qualifying a model
|
|
261
291
|
gets expensive, the fixture set is too big.
|
|
262
292
|
|
|
263
|
-
## Phase B
|
|
293
|
+
## Phase B: building the whitelist
|
|
264
294
|
|
|
265
295
|
With a provider configured (OpenRouter **paid** slugs only, never `:free`) and a
|
|
266
296
|
known-good `--reference`, run `neal compat --model <slug> --role all --json` for
|
|
267
297
|
each candidate and fill the dated [`compatible-models.md`](compatible-models.md)
|
|
268
|
-
matrix from the JSON output. Date and version the matrix
|
|
298
|
+
matrix from the JSON output. Date and version the matrix. Model behavior drifts,
|
|
269
299
|
so re-run on version bumps.
|