@navels/neal 0.1.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.
- package/LICENSE +21 -0
- package/README.md +527 -0
- package/SECURITY.md +91 -0
- package/config.yml +104 -0
- package/dist/neal/activity-footer.js +177 -0
- package/dist/neal/activity-reporting.js +1 -0
- package/dist/neal/adjudicator/artifacts.js +58 -0
- package/dist/neal/adjudicator/blocked-adjudicator.js +223 -0
- package/dist/neal/adjudicator/contracts.js +139 -0
- package/dist/neal/adjudicator/execute.js +611 -0
- package/dist/neal/adjudicator/final-completion.js +104 -0
- package/dist/neal/adjudicator/planning.js +145 -0
- package/dist/neal/adjudicator/specs.js +453 -0
- package/dist/neal/agents/prompts.js +120 -0
- package/dist/neal/agents/rounds.js +706 -0
- package/dist/neal/agents/schemas.js +832 -0
- package/dist/neal/agents/structured-coder.js +82 -0
- package/dist/neal/agents/structured-json.js +528 -0
- package/dist/neal/agents.js +4 -0
- package/dist/neal/atomic-write.js +18 -0
- package/dist/neal/blocked-guidance.js +406 -0
- package/dist/neal/cli.js +471 -0
- package/dist/neal/commands/check.js +401 -0
- package/dist/neal/commands/compat.js +807 -0
- package/dist/neal/commands/interactive-activity.js +57 -0
- package/dist/neal/commands/new-run.js +79 -0
- package/dist/neal/commands/plan-and-execute.js +44 -0
- package/dist/neal/commands/recovery-guidance.js +217 -0
- package/dist/neal/commands/resume-run.js +395 -0
- package/dist/neal/commands/review.js +21 -0
- package/dist/neal/commands/runtime.js +557 -0
- package/dist/neal/commands/setup.js +596 -0
- package/dist/neal/commands/squash.js +113 -0
- package/dist/neal/commands/status.js +33 -0
- package/dist/neal/commands/writer-exit-codes.js +42 -0
- package/dist/neal/commit-message.js +17 -0
- package/dist/neal/config.js +432 -0
- package/dist/neal/context/artifacts.js +140 -0
- package/dist/neal/context/context.js +324 -0
- package/dist/neal/context/inline-review-context.js +131 -0
- package/dist/neal/context/reviewer-context.js +166 -0
- package/dist/neal/context/shared.js +117 -0
- package/dist/neal/context/types.js +1 -0
- package/dist/neal/diagnostic.js +208 -0
- package/dist/neal/execute-finalization.js +5 -0
- package/dist/neal/final-completion-review.js +188 -0
- package/dist/neal/final-completion.js +229 -0
- package/dist/neal/git.js +339 -0
- package/dist/neal/index.js +135 -0
- package/dist/neal/interactive-controls.js +85 -0
- package/dist/neal/logger.js +102 -0
- package/dist/neal/manual-gates.js +121 -0
- package/dist/neal/orchestrator/artifacts.js +70 -0
- package/dist/neal/orchestrator/completion.js +531 -0
- package/dist/neal/orchestrator/failures.js +31 -0
- package/dist/neal/orchestrator/notifications.js +175 -0
- package/dist/neal/orchestrator/phases/coder.js +516 -0
- package/dist/neal/orchestrator/phases/planning.js +540 -0
- package/dist/neal/orchestrator/phases/recovery.js +798 -0
- package/dist/neal/orchestrator/phases/review.js +136 -0
- package/dist/neal/orchestrator/phases/shared.js +279 -0
- package/dist/neal/orchestrator/run-loop.js +113 -0
- package/dist/neal/orchestrator/split-plan.js +235 -0
- package/dist/neal/orchestrator/transitions.js +309 -0
- package/dist/neal/orchestrator.js +215 -0
- package/dist/neal/phase-display.js +27 -0
- package/dist/neal/plan-doc.js +154 -0
- package/dist/neal/plan-queue.js +1092 -0
- package/dist/neal/plan-refinement.js +39 -0
- package/dist/neal/plan-validation.js +525 -0
- package/dist/neal/progress.js +237 -0
- package/dist/neal/prompts/assert-builder.js +13 -0
- package/dist/neal/prompts/execute.js +290 -0
- package/dist/neal/prompts/guidance.js +70 -0
- package/dist/neal/prompts/planning.js +313 -0
- package/dist/neal/prompts/review-doctrine.js +142 -0
- package/dist/neal/prompts/shared.js +101 -0
- package/dist/neal/prompts/specialized.js +212 -0
- package/dist/neal/prompts/specs.js +572 -0
- package/dist/neal/providers/anthropic-claude.js +1599 -0
- package/dist/neal/providers/detection.js +139 -0
- package/dist/neal/providers/generic-agentic-tools.js +586 -0
- package/dist/neal/providers/generic-agentic.js +1238 -0
- package/dist/neal/providers/liveness.js +151 -0
- package/dist/neal/providers/openai-codex.js +1014 -0
- package/dist/neal/providers/openai-compatible.js +654 -0
- package/dist/neal/providers/registry.js +389 -0
- package/dist/neal/providers/telemetry.js +208 -0
- package/dist/neal/providers/types.js +21 -0
- package/dist/neal/recovery-artifacts.js +50 -0
- package/dist/neal/resume-decision.js +220 -0
- package/dist/neal/resume-planner.js +265 -0
- package/dist/neal/retrospective.js +391 -0
- package/dist/neal/review-debt.js +18 -0
- package/dist/neal/review-findings/artifacts.js +173 -0
- package/dist/neal/review-findings/prompts.js +172 -0
- package/dist/neal/review-findings/provider.js +330 -0
- package/dist/neal/review-findings/run.js +373 -0
- package/dist/neal/review-findings/types.js +1 -0
- package/dist/neal/review-mode.js +67 -0
- package/dist/neal/review.js +137 -0
- package/dist/neal/run-lock.js +334 -0
- package/dist/neal/run-metrics.js +355 -0
- package/dist/neal/run-narrative-types.js +1 -0
- package/dist/neal/run-narrative.js +1374 -0
- package/dist/neal/run-registry.js +218 -0
- package/dist/neal/run-status.js +25 -0
- package/dist/neal/scopes.js +451 -0
- package/dist/neal/sensitive-text.js +8 -0
- package/dist/neal/squash-message.js +379 -0
- package/dist/neal/squash.js +591 -0
- package/dist/neal/state-invariants.js +496 -0
- package/dist/neal/state-views.js +344 -0
- package/dist/neal/state.js +887 -0
- package/dist/neal/status-footer.js +258 -0
- package/dist/neal/status.js +1260 -0
- package/dist/neal/storage-paths.js +57 -0
- package/dist/neal/support.js +58 -0
- package/dist/neal/terminal-narrator.js +435 -0
- package/dist/neal/types.js +1 -0
- package/dist/neal/verification-events.js +81 -0
- package/dist/neal/version.js +37 -0
- package/dist/neal/worktree-status.js +137 -0
- package/dist/notifier.js +44 -0
- package/docs/ADJUDICATOR_INVENTORY.md +310 -0
- package/docs/PROMPT_SPECS.md +266 -0
- package/docs/README.md +22 -0
- package/docs/architecture.md +113 -0
- package/docs/assets/neal-execution-flow.png +0 -0
- package/docs/automation.md +65 -0
- package/docs/comparison.md +105 -0
- package/docs/compat.md +269 -0
- package/docs/compatible-models.md +135 -0
- package/docs/demo.md +55 -0
- package/docs/maintenance.md +64 -0
- package/docs/plan-format.md +213 -0
- package/docs/providers.md +751 -0
- package/docs/release.md +147 -0
- package/docs/state-machine.md +266 -0
- package/docs/storage.md +207 -0
- package/docs/troubleshooting.md +152 -0
- package/examples/compat/add-edit-verify/PLAN.md +29 -0
- package/examples/compat/add-edit-verify/broken.diff +8 -0
- package/examples/compat/add-edit-verify/good.diff +8 -0
- package/examples/compat/add-edit-verify/package.json +5 -0
- package/examples/compat/add-edit-verify/src/add.js +2 -0
- package/examples/compat/add-edit-verify/test/add.test.js +9 -0
- package/examples/compat/is-even-add-test/PLAN.md +30 -0
- package/examples/compat/is-even-add-test/broken.diff +11 -0
- package/examples/compat/is-even-add-test/good.diff +11 -0
- package/examples/compat/is-even-add-test/package.json +5 -0
- package/examples/compat/is-even-add-test/src/is-even.js +3 -0
- package/examples/compat/is-even-add-test/test/is-even.test.js +9 -0
- package/examples/compat/manifest.json +60 -0
- package/examples/compat/plan-greeting/ISSUE.md +25 -0
- package/examples/compat/plan-greeting/package.json +5 -0
- package/examples/compat/plan-greeting/src/greet.js +2 -0
- package/examples/compat/plan-greeting/test/greet.test.js +8 -0
- package/examples/compat/reverse-grep-edit/PLAN.md +32 -0
- package/examples/compat/reverse-grep-edit/broken.diff +12 -0
- package/examples/compat/reverse-grep-edit/good.diff +12 -0
- package/examples/compat/reverse-grep-edit/package.json +5 -0
- package/examples/compat/reverse-grep-edit/src/strings.js +10 -0
- package/examples/compat/reverse-grep-edit/test/strings.test.js +15 -0
- package/examples/issue-triage-js/PLAN.md +83 -0
- package/examples/issue-triage-js/README.md +76 -0
- package/examples/issue-triage-js/package.json +9 -0
- package/examples/issue-triage-js/src/issue-triage.js +87 -0
- package/examples/issue-triage-js/test/issue-triage.test.js +107 -0
- package/package.json +70 -0
package/docs/compat.md
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# `neal compat` — model compatibility smoke test
|
|
2
|
+
|
|
3
|
+
`neal compat` answers one question per model: **can this OpenAI-compatible model
|
|
4
|
+
drive neal's loop at all** — as a coder, a reviewer, or a planner? It is a
|
|
5
|
+
**compatibility smoke test, not a performance benchmark**. There is no
|
|
6
|
+
resolve-rate, no score, no ranking, and no real SWE-bench repo involved. The
|
|
7
|
+
fixtures are deliberately trivial; any working agent should pass them. A failure
|
|
8
|
+
therefore means something is **fundamentally wrong** with the model's ability to
|
|
9
|
+
operate neal's contract — not that a task was hard.
|
|
10
|
+
|
|
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 — no
|
|
13
|
+
external benchmark assets, no dataset download, no remote runner.
|
|
14
|
+
|
|
15
|
+
## Compatibility, not skill
|
|
16
|
+
|
|
17
|
+
- **Measured:** whether the model can follow neal's contract end to end (valid
|
|
18
|
+
tool calls + structured output, no max-step loops, no unresolved operator
|
|
19
|
+
blocks, a clean finalization) **and** solve a trivial bundled fixture.
|
|
20
|
+
- **Not measured:** skill. No resolve-rate, no scores, no rankings, no real
|
|
21
|
+
SWE-bench repos — trivial fixtures only.
|
|
22
|
+
- **Outcome:** a binary **PASS / FAIL per (model, role)**, with a structural
|
|
23
|
+
failure mode recorded for every FAIL.
|
|
24
|
+
|
|
25
|
+
## How it layers with `neal check`
|
|
26
|
+
|
|
27
|
+
`neal check` and `neal compat` are two distinct gates:
|
|
28
|
+
|
|
29
|
+
- **`neal check`** validates the effective config and confirms each configured
|
|
30
|
+
role's provider can answer one small structured probe
|
|
31
|
+
(`provider_check_payload`). It proves *connectivity and basic protocol*, not
|
|
32
|
+
that the model can complete a full neal run. When the resolved **coder or
|
|
33
|
+
reviewer** role uses a non-native (generic-agentic / openai-compatible)
|
|
34
|
+
provider, `neal check` prints a one-line pointer: *"This is a generic-agentic
|
|
35
|
+
model - run `neal compat` to confirm it can drive the full loop."* Native
|
|
36
|
+
adapters (`openai-codex`, `anthropic-claude`) do not get the pointer. The
|
|
37
|
+
planner inherits the coder provider by default, so the coder check covers the
|
|
38
|
+
common case; an explicitly configured non-native planner paired with native
|
|
39
|
+
coder/reviewer is not separately flagged (run `neal compat --role planner`
|
|
40
|
+
to qualify it).
|
|
41
|
+
- **`neal compat`** runs the same structured pre-filter first (it reuses
|
|
42
|
+
`check`'s `verifyConfiguredProviders`), then drives the model through complete
|
|
43
|
+
neal runs against the bundled fixtures and produces a PASS/FAIL matrix.
|
|
44
|
+
|
|
45
|
+
So: `neal check` is the cheap connectivity gate; `neal compat` is the full-loop
|
|
46
|
+
qualification gate that `check` points generic-agentic users toward.
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
neal compat [--model <slug>] [--role coder|reviewer|planner|all] [--reference openai-codex|anthropic-claude|generic-agentic:<slug>] [--json]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- `--role` (default `all`) selects which role(s) to test: `coder`, `reviewer`,
|
|
55
|
+
`planner`, or `all`.
|
|
56
|
+
- `--model <slug>` runs the slug on the **`generic-agentic`** provider in the
|
|
57
|
+
candidate role (provider forced to `generic-agentic`, any configured effort
|
|
58
|
+
dropped, so the slug drives a clean OpenRouter call). When omitted, the
|
|
59
|
+
candidate role uses its configured provider/model unchanged.
|
|
60
|
+
- `--reference <id>` names the provider for the **non-candidate** roles so a
|
|
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 — `openai-codex`
|
|
63
|
+
(the default when omitted while `--model` is set) or `anthropic-claude` — or
|
|
64
|
+
`generic-agentic:<openrouter-model>` to run the reference roles on an
|
|
65
|
+
OpenRouter model. A bare model slug is rejected. Native reference roles run on
|
|
66
|
+
that adapter's default model (`gpt-5.5` for `openai-codex`, `claude-opus-4-8`
|
|
67
|
+
for `anthropic-claude`). Prefer a native reference for whitelist
|
|
68
|
+
qualification; see [compatible-models.md](compatible-models.md) for why
|
|
69
|
+
OpenRouter references proved unreliable as qualification partners. When
|
|
70
|
+
**neither** `--model` nor `--reference` is given, every role stays on its
|
|
71
|
+
configured provider/model (pure pass-through).
|
|
72
|
+
- `--json` prints the stable machine-readable matrix instead of the human table
|
|
73
|
+
(schema below).
|
|
74
|
+
|
|
75
|
+
The command resolves its base config via the same writer-provider resolver the
|
|
76
|
+
other commands use, then derives a candidate config by cloning it and routing the
|
|
77
|
+
tested role onto `generic-agentic` (when `--model` is set) and the non-candidate
|
|
78
|
+
roles onto the native reference adapter (when either flag is set). It runs
|
|
79
|
+
against **throwaway git copies** of the bundled fixtures and **never mutates** a
|
|
80
|
+
committed fixture under `examples/compat/`. The process exits non-zero when the
|
|
81
|
+
overall result is FAIL.
|
|
82
|
+
|
|
83
|
+
Example (Phase B operational usage, OpenRouter paid slug with a native reference):
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
neal compat --model deepseek/deepseek-chat --role all --reference openai-codex --json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Bundled fixtures (`examples/compat/`)
|
|
90
|
+
|
|
91
|
+
Each fixture is a tiny self-contained project. `examples/compat/manifest.json`
|
|
92
|
+
is the single data source that makes the command data-driven. The shipped
|
|
93
|
+
fixtures are:
|
|
94
|
+
|
|
95
|
+
| Fixture | Roles | Contract surface |
|
|
96
|
+
| --- | --- | --- |
|
|
97
|
+
| `add-edit-verify` | coder, reviewer | edit + verify + commit |
|
|
98
|
+
| `reverse-grep-edit` | coder, reviewer | read/grep-then-edit |
|
|
99
|
+
| `is-even-add-test` | coder, reviewer | add-a-test |
|
|
100
|
+
| `plan-greeting` | planner | emit a `one_shot` plan |
|
|
101
|
+
|
|
102
|
+
Each coder fixture bundles buggy source, a test that fails against the buggy
|
|
103
|
+
source, and a `PLAN.md` declaring `executionShape: one_shot` that describes the
|
|
104
|
+
smallest complete fix. The reviewer fixtures reuse the same projects, each paired
|
|
105
|
+
with two pre-made unified diffs (`good.diff`, `broken.diff`). The planner fixture
|
|
106
|
+
provides an issue-statement seed (`ISSUE.md`) and no reviewer diffs.
|
|
107
|
+
|
|
108
|
+
`examples/issue-triage-js` remains an optional tier-2 "real-ish" check above the
|
|
109
|
+
trivial gate; `neal compat` does not require it.
|
|
110
|
+
|
|
111
|
+
### Manifest schema
|
|
112
|
+
|
|
113
|
+
```jsonc
|
|
114
|
+
{
|
|
115
|
+
"fixtures": [
|
|
116
|
+
{
|
|
117
|
+
"id": "add-edit-verify",
|
|
118
|
+
"roles": ["coder", "reviewer"], // which roles use this fixture
|
|
119
|
+
"projectDir": "add-edit-verify", // relative to examples/compat/
|
|
120
|
+
"planDoc": "add-edit-verify/PLAN.md", // coder run plan (one_shot)
|
|
121
|
+
"verifyCommand": "node --test test/add.test.js", // coder PASS check
|
|
122
|
+
"referenceFix": { "file": "src/add.js", "from": "a - b", "to": "a + b" },
|
|
123
|
+
"reviewer": {
|
|
124
|
+
"goodDiff": "add-edit-verify/good.diff", // reviewer must ACCEPT
|
|
125
|
+
"brokenDiff": "add-edit-verify/broken.diff" // reviewer must FLAG/REJECT
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
A planner fixture sets `"roles": ["planner"]` and provides `"issuePrompt"`
|
|
133
|
+
instead of `reviewer`. Any fixture carrying the `reviewer` role must also define
|
|
134
|
+
`verifyCommand`: reviewer fixtures reuse the coder project's command as the
|
|
135
|
+
deterministic good/broken oracle — `goodDiff` must make it pass, `brokenDiff`
|
|
136
|
+
must make it fail. The `referenceFix` is used only by the structural fixture
|
|
137
|
+
self-test (`test/compat-fixtures.test.ts`), never by a model run.
|
|
138
|
+
|
|
139
|
+
## Definition of PASS (per model, per role)
|
|
140
|
+
|
|
141
|
+
Across **every** fixture for the role, the model must (1) **complete the run
|
|
142
|
+
cleanly** — the run reaches `status: 'done'` (not `'failed'` / `'blocked'`), does
|
|
143
|
+
not emit an `unattended.block_unresolved` event, and does not exceed its
|
|
144
|
+
step/round budget — **and** (2) produce the **role-correct result**:
|
|
145
|
+
|
|
146
|
+
- **coder:** `finalState.status === 'done'` **and** the fixture's
|
|
147
|
+
`verifyCommand` exits `0` in the throwaway worktree (the failing test now
|
|
148
|
+
passes).
|
|
149
|
+
- **reviewer:** the review-findings loop converges (`outcome === 'accepted'`) for
|
|
150
|
+
**both** diffs, and the converged findings discriminate them: **zero** blocking
|
|
151
|
+
findings for the good diff and **≥1** blocking finding (a `ReviewFindingItem`
|
|
152
|
+
with `severity === 'blocking'`) for the broken diff. `outcome` alone is not
|
|
153
|
+
sufficient — it is `'accepted'` whenever the findings artifact converges,
|
|
154
|
+
including a zero-finding artifact — so the good/broken verdict is scored on
|
|
155
|
+
`draft.findings` severities.
|
|
156
|
+
- **planner** (secondary): the emitted plan document validates via
|
|
157
|
+
`validatePlanDocument` as a schema-conformant `one_shot` plan.
|
|
158
|
+
|
|
159
|
+
Any single fundamental failure on any fixture for the role → **FAIL** for that
|
|
160
|
+
role, with the mode recorded.
|
|
161
|
+
|
|
162
|
+
`neal compat` forces **unattended mode** on every run so a model is never
|
|
163
|
+
penalized for a halt that a human operator would simply have resumed.
|
|
164
|
+
|
|
165
|
+
## Failure-mode taxonomy
|
|
166
|
+
|
|
167
|
+
Each FAIL records a mode derived from **structural run state** — `finalState.status`,
|
|
168
|
+
emitted run events, `ReviewFindingsOutcome`, and `validatePlanDocument` — never
|
|
169
|
+
from substring-matching model prose. When more than one applies, the earliest in
|
|
170
|
+
this list (most specific cause first) is recorded:
|
|
171
|
+
|
|
172
|
+
- `protocol` — the `verifyConfiguredProviders` pre-filter threw for the candidate
|
|
173
|
+
(it could not emit one valid `provider_check_payload`); fixtures were skipped.
|
|
174
|
+
- `provider_failed` — the run failed with a model-attributable provider error
|
|
175
|
+
event (transport/auth/transient/other) rather than a clean completion, **except**
|
|
176
|
+
a provider error whose `errorKind` is a structured-output kind, which is recorded
|
|
177
|
+
as `structured_output` (below). This is also the bucket for a **writer** run that
|
|
178
|
+
ends `status: 'failed'` without a more specific structural signal — including
|
|
179
|
+
step/round-budget exhaustion, which the current runtime does not surface to compat
|
|
180
|
+
as a distinct cap event, so writer step-cap exhaustion is reported here rather than
|
|
181
|
+
as `max_step_loop`.
|
|
182
|
+
- `block_unresolved` — the run emitted `unattended.block_unresolved` (the model
|
|
183
|
+
escalated to an operator block unattended mode could not resolve within budget),
|
|
184
|
+
or the review loop's outcome was `'blocked'`.
|
|
185
|
+
- `max_step_loop` — the **reviewer** loop's outcome was `'cap_reached'` (the
|
|
186
|
+
review-findings convergence cap was hit). Writer (coder/planner) step-cap
|
|
187
|
+
exhaustion is not separately distinguishable under the current runtime and is
|
|
188
|
+
classified as `provider_failed` (above).
|
|
189
|
+
- `wrong_or_empty_output` — coder: the run reached `done` but `verifyCommand`
|
|
190
|
+
exited non-zero (or the diff was empty); reviewer: the verdict was wrong
|
|
191
|
+
(accepted the broken diff with no blocking finding, or flagged the good diff
|
|
192
|
+
with a blocking finding); planner: the emitted plan was not a schema-conformant
|
|
193
|
+
`one_shot` plan.
|
|
194
|
+
- `structured_output` — the model could not produce or honor **schema-enforced**
|
|
195
|
+
JSON. This covers a reviewer/planner round whose payload failed schema validation
|
|
196
|
+
(surfaced as the corresponding round error) **and** a coder/reviewer provider
|
|
197
|
+
error whose `errorKind` is `structured_output_invalid` or
|
|
198
|
+
`structured_output_missing` — e.g. an OpenRouter (generic-agentic) gateway that
|
|
199
|
+
rejects the `type: 'json_schema'` request (HTTP 400) or returns a missing/invalid
|
|
200
|
+
object. This is distinct from `provider_failed` (transport/auth/other transport
|
|
201
|
+
errors) and from `wrong_or_empty_output` (a syntactically valid verdict/output
|
|
202
|
+
that was substantively wrong). Both the coder (`classifyWriterFailure`) and
|
|
203
|
+
reviewer (`classifyReviewerThrownFailure`) paths attribute these kinds here.
|
|
204
|
+
- `finalization_error` — the run failed during finalization/artifact writing, or
|
|
205
|
+
the run produced no final document where one was expected.
|
|
206
|
+
|
|
207
|
+
When no structural signal is conclusive, `provider_failed` is recorded with the
|
|
208
|
+
terminal error message attached.
|
|
209
|
+
|
|
210
|
+
## Output
|
|
211
|
+
|
|
212
|
+
By default `neal compat` prints a human-readable matrix: one row per
|
|
213
|
+
role × fixture cell (with the reviewer `good`/`broken` diff kind), a per-role
|
|
214
|
+
roll-up, and an overall verdict.
|
|
215
|
+
|
|
216
|
+
### `--json` schema
|
|
217
|
+
|
|
218
|
+
`--json` prints a single JSON object. This shape is stable and feeds the Phase B
|
|
219
|
+
whitelist (`docs/compatible-models.md`):
|
|
220
|
+
|
|
221
|
+
```jsonc
|
|
222
|
+
{
|
|
223
|
+
"schemaVersion": 1,
|
|
224
|
+
"model": "deepseek/deepseek-chat", // candidate --model, or null when omitted
|
|
225
|
+
"reference": null, // native provider id (openai-codex | anthropic-claude), or null when omitted
|
|
226
|
+
"role": "all", // coder | reviewer | planner | all
|
|
227
|
+
"candidateProviders": { // provider each role used as the candidate: "generic-agentic" per role when --model is set, else the configured provider
|
|
228
|
+
"coder": "generic-agentic",
|
|
229
|
+
"reviewer": "generic-agentic",
|
|
230
|
+
"planner": "generic-agentic"
|
|
231
|
+
},
|
|
232
|
+
"cells": [
|
|
233
|
+
{
|
|
234
|
+
"role": "coder", // coder | reviewer | planner
|
|
235
|
+
"fixtureId": "add-edit-verify", // manifest id; "provider:<role>" for a protocol pre-filter FAIL
|
|
236
|
+
"diffKind": null, // "good" | "broken" for reviewer cells, else null
|
|
237
|
+
"pass": true,
|
|
238
|
+
"failureMode": null, // one of the taxonomy modes when pass=false, else null
|
|
239
|
+
"detail": null // human-readable explanation when pass=false, else null
|
|
240
|
+
}
|
|
241
|
+
],
|
|
242
|
+
"roles": [
|
|
243
|
+
{
|
|
244
|
+
"role": "coder",
|
|
245
|
+
"pass": true,
|
|
246
|
+
"cellCount": 3, // cells for this role
|
|
247
|
+
"passCount": 3 // cells that passed
|
|
248
|
+
}
|
|
249
|
+
],
|
|
250
|
+
"overallPass": true // true iff every targeted role's roll-up passed
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
A role roll-up is PASS iff it has at least one cell and every one of its cells
|
|
255
|
+
passed. `overallPass` is true iff every targeted role roll-up passed. The process
|
|
256
|
+
exits non-zero when `overallPass` is false.
|
|
257
|
+
|
|
258
|
+
## Cost
|
|
259
|
+
|
|
260
|
+
Cheap by construction: trivial fixtures and cheap models. If qualifying a model
|
|
261
|
+
gets expensive, the fixture set is too big.
|
|
262
|
+
|
|
263
|
+
## Phase B — building the whitelist
|
|
264
|
+
|
|
265
|
+
With a provider configured (OpenRouter **paid** slugs only, never `:free`) and a
|
|
266
|
+
known-good `--reference`, run `neal compat --model <slug> --role all --json` for
|
|
267
|
+
each candidate and fill the dated [`compatible-models.md`](compatible-models.md)
|
|
268
|
+
matrix from the JSON output. Date and version the matrix — model behavior drifts,
|
|
269
|
+
so re-run on version bumps.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Compatible models
|
|
2
|
+
|
|
3
|
+
> **Last updated:** 2026-06-18 · **Reference:** `openai-codex` (gpt-5.5) · **Pool:** 79 OpenRouter models
|
|
4
|
+
>
|
|
5
|
+
> The whitelist from running [`neal compat`](compat.md) across an OpenRouter candidate
|
|
6
|
+
> pool. It records **compatibility, not skill**: a PASS means the model can drive neal's
|
|
7
|
+
> loop on the trivial bundled fixtures in that role — it says nothing about how well the
|
|
8
|
+
> model performs on real work. Choose among the PASSes by your own cost/quality needs.
|
|
9
|
+
|
|
10
|
+
## How to read this
|
|
11
|
+
|
|
12
|
+
- A PASS means the model drove neal under **real schema-enforced structured output**
|
|
13
|
+
(`type: 'json_schema'`), with `openai-codex` (gpt-5.5) running the non-tested roles as a
|
|
14
|
+
known-good partner. Role cells = passed/total fixtures (coder 3, reviewer 6, planner 1).
|
|
15
|
+
- A FAIL is **attributable** to the model via its [failure mode](compat.md#failure-mode-taxonomy)
|
|
16
|
+
(e.g. `structured_output`, not a generic `provider_failed`).
|
|
17
|
+
- **Single-run results are noisy at the margin.** Models that fail only 1–2 cells, or fail
|
|
18
|
+
with `provider error`, often flip on a re-run — treat the "borderline" table as *re-run
|
|
19
|
+
candidates*, not verdicts.
|
|
20
|
+
- Native adapters (`openai-codex`, `anthropic-claude`) are supported by construction and
|
|
21
|
+
need no compat run.
|
|
22
|
+
|
|
23
|
+
## ✅ Compatible (25) — verified across all roles
|
|
24
|
+
|
|
25
|
+
Sorted cheapest first ($/Mtok in·out, from the live OpenRouter catalog).
|
|
26
|
+
|
|
27
|
+
| Model | Coder | Reviewer | Planner | $/Mtok | ctx |
|
|
28
|
+
|---|---|---|---|---|---|
|
|
29
|
+
| `google/gemma-4-26b-a4b-it` | 3/3 | 6/6 | 1/1 | 0.06·0.33 | 262k |
|
|
30
|
+
| `inclusionai/ling-2.6-1t` | 3/3 | 6/6 | 1/1 | 0.07·0.62 | 262k |
|
|
31
|
+
| `deepseek/deepseek-v4-flash` | 3/3 | 6/6 | 1/1 | 0.09·0.18 | 1048k |
|
|
32
|
+
| `nvidia/nemotron-3-super-120b-a12b` | 3/3 | 6/6 | 1/1 | 0.09·0.45 | 1000k |
|
|
33
|
+
| `google/gemma-4-31b-it` | 3/3 | 6/6 | 1/1 | 0.12·0.35 | 262k |
|
|
34
|
+
| `qwen/qwen3.6-35b-a3b` | 3/3 | 6/6 | 1/1 | 0.14·1.00 | 262k |
|
|
35
|
+
| `minimax/minimax-m2.5` | 3/3 | 6/6 | 1/1 | 0.15·0.90 | 204k |
|
|
36
|
+
| `deepseek/deepseek-chat-v3.1` | 3/3 | 6/6 | 1/1 | 0.21·0.79 | 163k |
|
|
37
|
+
| `deepseek/deepseek-v3.2` | 3/3 | 6/6 | 1/1 | 0.23·0.34 | 131k |
|
|
38
|
+
| `bytedance-seed/seed-2.0-lite` | 3/3 | 6/6 | 1/1 | 0.25·2.00 | 262k |
|
|
39
|
+
| `google/gemini-3.1-flash-lite` | 3/3 | 6/6 | 1/1 | 0.25·1.50 | 1048k |
|
|
40
|
+
| `minimax/minimax-m2.7` | 3/3 | 6/6 | 1/1 | 0.25·1.00 | 204k |
|
|
41
|
+
| `google/gemini-3.1-flash-lite-preview` | 3/3 | 6/6 | 1/1 | 0.25·1.50 | 1048k |
|
|
42
|
+
| `minimax/minimax-m2` | 3/3 | 6/6 | 1/1 | 0.26·1.00 | 204k |
|
|
43
|
+
| `deepseek/deepseek-v3.1-terminus` | 3/3 | 6/6 | 1/1 | 0.27·0.95 | 163k |
|
|
44
|
+
| `minimax/minimax-m2.1` | 3/3 | 6/6 | 1/1 | 0.29·0.95 | 204k |
|
|
45
|
+
| `minimax/minimax-m3` | 3/3 | 6/6 | 1/1 | 0.30·1.20 | 1048k |
|
|
46
|
+
| `qwen/qwen3.7-plus` | 3/3 | 6/6 | 1/1 | 0.32·1.28 | 1000k |
|
|
47
|
+
| `z-ai/glm-5` | 3/3 | 6/6 | 1/1 | 0.60·1.92 | 202k |
|
|
48
|
+
| `z-ai/glm-5.1` | 3/3 | 6/6 | 1/1 | 0.98·3.08 | 202k |
|
|
49
|
+
| `qwen/qwen3.6-max-preview` | 3/3 | 6/6 | 1/1 | 1.04·6.24 | 262k |
|
|
50
|
+
| `x-ai/grok-4.20` | 3/3 | 6/6 | 1/1 | 1.25·2.50 | 2000k |
|
|
51
|
+
| `x-ai/grok-4.3` | 3/3 | 6/6 | 1/1 | 1.25·2.50 | 1000k |
|
|
52
|
+
| `google/gemini-3.5-flash` | 3/3 | 6/6 | 1/1 | 1.50·9.00 | 1048k |
|
|
53
|
+
| `google/gemini-3.1-pro-preview-customtools` | 3/3 | 6/6 | 1/1 | 2.00·12.00 | 1048k |
|
|
54
|
+
|
|
55
|
+
## ⚠️ Borderline — re-run candidates (failed 1–2 cells, or transient)
|
|
56
|
+
|
|
57
|
+
These drove most of neal but tripped on one or two cells. `provider error` rows are
|
|
58
|
+
transient (re-run likely flips them); `bad review output` / `can't emit schema JSON` are
|
|
59
|
+
single-run signals that often flip too. Re-run before excluding.
|
|
60
|
+
|
|
61
|
+
| Model | Coder | Reviewer | Planner | Tripped on |
|
|
62
|
+
|---|---|---|---|---|
|
|
63
|
+
| `mistralai/devstral-2512` | 3/3 | 6/6 | 0/1 | planner: provider error |
|
|
64
|
+
| `nvidia/nemotron-3-ultra-550b-a55b` | 3/3 | 5/6 | 1/1 | reviewer: provider error |
|
|
65
|
+
| `x-ai/grok-build-0.1` | 3/3 | 5/6 | 1/1 | reviewer: provider error |
|
|
66
|
+
| `nvidia/nemotron-3-nano-30b-a3b` | 3/3 | 5/6 | 1/1 | reviewer: bad review output |
|
|
67
|
+
| `deepseek/deepseek-v3.2-exp` | 3/3 | 5/6 | 1/1 | reviewer: bad review output |
|
|
68
|
+
| `mistralai/mistral-medium-3-5` | 3/3 | 5/6 | 1/1 | reviewer: bad review output |
|
|
69
|
+
| `qwen/qwen3.5-plus-20260420` | 3/3 | 5/6 | 1/1 | reviewer: bad review output |
|
|
70
|
+
| `qwen/qwen3.7-max` | 3/3 | 5/6 | 1/1 | reviewer: bad review output |
|
|
71
|
+
| `qwen/qwen3.6-flash` | 3/3 | 5/6 | 1/1 | reviewer: can't emit schema JSON |
|
|
72
|
+
| `deepseek/deepseek-v4-pro` | 3/3 | 4/6 | 1/1 | reviewer: can't emit schema JSON |
|
|
73
|
+
| `meta-llama/llama-4-maverick` | 1/3 | 6/6 | 1/1 | coder: left block unresolved |
|
|
74
|
+
| `moonshotai/kimi-k2.5` | 2/3 | 6/6 | 1/1 | coder: can't emit schema JSON |
|
|
75
|
+
| `stepfun/step-3.7-flash` | 2/3 | 6/6 | 1/1 | coder: provider error |
|
|
76
|
+
| `inclusionai/ring-2.6-1t` | 2/3 | 5/6 | 1/1 | coder+reviewer: schema JSON |
|
|
77
|
+
| `z-ai/glm-5.2` | 2/3 | 5/6 | 1/1 | coder+reviewer: schema JSON |
|
|
78
|
+
| `inclusionai/ling-2.6-flash` | 0/3 | 4/6 | 1/1 | coder+reviewer: provider error |
|
|
79
|
+
| `meta-llama/llama-3.3-70b-instruct` | 0/3 | 3/6 | 0/1 | provider error |
|
|
80
|
+
| `bytedance-seed/seed-1.6` | 0/3 | 5/6 | 1/1 | coder: can't emit schema JSON |
|
|
81
|
+
| `bytedance-seed/seed-1.6-flash` | 0/3 | 5/6 | 0/1 | coder: can't emit schema JSON |
|
|
82
|
+
|
|
83
|
+
> Notable: the flagship `deepseek-v4-pro` FAILs (reviewer schema), but the cheaper
|
|
84
|
+
> `deepseek-v4-flash` / `v3.2` PASS cleanly — and v4-pro has flaked stochastically before,
|
|
85
|
+
> so it's a re-run candidate, not a hard FAIL.
|
|
86
|
+
|
|
87
|
+
## ❌ Incompatible — fail the provider handshake (no tool-use / unparsable JSON)
|
|
88
|
+
|
|
89
|
+
These can't drive neal's agentic loop on OpenRouter at all (`protocol` failure on the
|
|
90
|
+
provider check — 0/1 every role): mostly small models and ones whose OR endpoints don't
|
|
91
|
+
support tool use.
|
|
92
|
+
|
|
93
|
+
`amazon/nova-{micro,lite,2-lite,pro,premier}-v1`, `bytedance-seed/seed-2.0-mini`,
|
|
94
|
+
`cohere/{command-a,command-r-08-2024,command-r-plus-08-2024,command-r7b-12-2024}`,
|
|
95
|
+
`meta-llama/{llama-3.1-8b,llama-3.2-1b,llama-3.2-3b}-instruct`, `meta-llama/llama-4-scout`,
|
|
96
|
+
`microsoft/{phi-4,phi-4-mini-instruct,wizardlm-2-8x22b}`, `minimax/minimax-m2-her`,
|
|
97
|
+
`mistralai/{ministral-3b,ministral-8b,ministral-14b}-2512`, `mistralai/mistral-small-2603`,
|
|
98
|
+
`moonshotai/{kimi-k2,kimi-k2-0905,kimi-k2-thinking,kimi-k2.6,kimi-k2.7-code}`,
|
|
99
|
+
`nvidia/llama-3.3-nemotron-super-49b-v1.5`, `openai/{gpt-oss-120b,gpt-oss-20b,gpt-oss-safeguard-20b}`,
|
|
100
|
+
`stepfun/step-3.5-flash`, `x-ai/grok-4.20-multi-agent`, `z-ai/{glm-4.7-flash,glm-5-turbo}`.
|
|
101
|
+
|
|
102
|
+
## Native adapters (supported, no compat run required)
|
|
103
|
+
|
|
104
|
+
| Provider id | Coder | Reviewer | Planner |
|
|
105
|
+
| --- | --- | --- | --- |
|
|
106
|
+
| `openai-codex` | supported | supported | supported |
|
|
107
|
+
| `anthropic-claude` | supported | supported | supported |
|
|
108
|
+
|
|
109
|
+
## Reproducing / extending
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
neal compat --model <slug> --role all --reference openai-codex --json
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`neal compat` also accepts an OpenRouter model as the reference
|
|
116
|
+
(`--reference generic-agentic:<slug>`) to escape codex capacity limits, but
|
|
117
|
+
**no OpenRouter model has validated as a drop-in reference** — two were tried on
|
|
118
|
+
2026-06-18 and both failed:
|
|
119
|
+
|
|
120
|
+
- `deepseek/deepseek-v4-flash` (cheap/fast) reproduced only **9** of the codex-ref
|
|
121
|
+
**25** PASSes (13 dropped, confirmed in isolation — not a rate-limit artifact):
|
|
122
|
+
too weak, it emits coder diffs the candidate-reviewer flags and blocks good
|
|
123
|
+
candidate work as partner-reviewer.
|
|
124
|
+
- `google/gemini-3.1-pro-preview-customtools` (strong/expensive) was worse in
|
|
125
|
+
practice: of the 25 codex-PASS models only **1** reproduced, and it **wedged
|
|
126
|
+
under load** (hung reference calls at 6-wide, timeouts/no-result on the
|
|
127
|
+
PASS-class models at 3-wide). A reference that can't run wide defeats the
|
|
128
|
+
throughput purpose of going off-codex in the first place.
|
|
129
|
+
|
|
130
|
+
**Conclusion:** `openai-codex` (gpt-5.5) is the authoritative reference. To extend
|
|
131
|
+
the whitelist beyond this pool, run additional candidates on codex-ref directly
|
|
132
|
+
(throttle to about three concurrent `neal compat` invocations for codex
|
|
133
|
+
capacity); the OpenRouter-reference path is not a viable shortcut today.
|
|
134
|
+
|
|
135
|
+
Paid slugs only — never `:free`. Re-run when a slug's backing model version changes.
|
package/docs/demo.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Demo Recording
|
|
2
|
+
|
|
3
|
+
Use `asciinema` for public terminal demos when a recording is useful. It is an
|
|
4
|
+
optional recording tool, not a package dependency.
|
|
5
|
+
|
|
6
|
+
Record from a disposable repository or throwaway worktree, preferably under
|
|
7
|
+
`/tmp`, so the demo does not expose real project history, paths, or artifacts.
|
|
8
|
+
|
|
9
|
+
## Suggested Script
|
|
10
|
+
|
|
11
|
+
Create a small demo plan in the disposable repository:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
throwaway="$(mktemp -d /tmp/neal-demo.XXXXXX)"
|
|
15
|
+
recording_dir="$(mktemp -d /tmp/neal-demo-recording.XXXXXX)"
|
|
16
|
+
recording="$recording_dir/neal-demo.cast"
|
|
17
|
+
cd "$throwaway"
|
|
18
|
+
git init
|
|
19
|
+
printf '# Neal demo target\n' > README.md
|
|
20
|
+
git add README.md
|
|
21
|
+
git -c user.name='Neal Demo' -c user.email='neal-demo@example.invalid' commit -m 'Initial demo baseline'
|
|
22
|
+
mkdir -p tmp
|
|
23
|
+
$EDITOR tmp/DEMO_PLAN.md
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Configure providers and run `neal check` before recording, or record those
|
|
27
|
+
steps only if the output is safe to share. Use current Neal commands while
|
|
28
|
+
recording:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
asciinema rec "$recording"
|
|
32
|
+
neal run tmp/DEMO_PLAN.md
|
|
33
|
+
neal status
|
|
34
|
+
exit
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Keep the recording output outside the demo repository so it does not appear as
|
|
38
|
+
unrelated dirty work while Neal starts the writer run.
|
|
39
|
+
|
|
40
|
+
If you specifically want to show recovery from a run that is waiting for
|
|
41
|
+
operator guidance, record that as a separate segment with the selected run id:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
neal resume --run <run-id> --message "Continue with the smallest safe follow-up."
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Scrubbing Checklist
|
|
48
|
+
|
|
49
|
+
Before sharing a recording, review the terminal output for local paths, private
|
|
50
|
+
project names, provider output, credentials, tokens, provider environment
|
|
51
|
+
variables, and `.neal/` artifact contents. Re-record from a clean disposable
|
|
52
|
+
repository if the output exposes anything sensitive.
|
|
53
|
+
|
|
54
|
+
This repository should commit recording instructions only, not `.cast` files or
|
|
55
|
+
generated terminal transcripts.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Maintenance: dependencies & versioning
|
|
2
|
+
|
|
3
|
+
neal's behavior is defined in large part by the agent SDKs it drives, so
|
|
4
|
+
dependency updates are a first-class concern, not routine hygiene. This document
|
|
5
|
+
is the policy.
|
|
6
|
+
|
|
7
|
+
## Two dependency tiers
|
|
8
|
+
|
|
9
|
+
| Tier | Packages | Pinning | Update posture |
|
|
10
|
+
| --- | --- | --- | --- |
|
|
11
|
+
| **Behavior-defining** | `@openai/codex-sdk`, `@anthropic-ai/claude-agent-sdk`, `@anthropic-ai/sdk`, `ai`, `@ai-sdk/openai-compatible`, `zod` | **exact** (enforced by `scripts/validate-release.mjs`) | **deliberate** — qualify before adopting; never auto-merge |
|
|
12
|
+
| **Utility** | `dotenv`, `yaml`, `@types/node`, `tsx`, `typescript` | caret OK | routine — auto-merge on green CI after a soak period |
|
|
13
|
+
|
|
14
|
+
The asymmetry exists because an agentic-SDK bump can change tool-calling,
|
|
15
|
+
structured output, or sandbox behavior — i.e. break neal's loop **without**
|
|
16
|
+
breaking compilation. Utility deps cannot.
|
|
17
|
+
|
|
18
|
+
## The update flow
|
|
19
|
+
|
|
20
|
+
1. **Detect.** [Renovate](../renovate.json) opens PRs weekly: utility deps grouped
|
|
21
|
+
(auto-merge), agentic SDKs one-per-PR (labelled `agentic-sdk` /
|
|
22
|
+
`needs-qualification`, auto-merge **off**, pinned exact).
|
|
23
|
+
2. **Verify (automatic).** CI (`.github/workflows/ci.yml`) runs typecheck + unit
|
|
24
|
+
tests + package verification — catches **API-shape / contract** breaks.
|
|
25
|
+
3. **Verify (behavioral).**
|
|
26
|
+
- The **AI-SDK tier** (`ai`, `@ai-sdk/openai-compatible`, `zod`) is exercised
|
|
27
|
+
automatically by the live smoke (`.github/workflows/smoke.yml`): a real
|
|
28
|
+
`neal compat` run against a cheap OpenRouter model through `generic-agentic`.
|
|
29
|
+
- The **native tier** (`@openai/codex-sdk`, `@anthropic-ai/*`) cannot be smoked
|
|
30
|
+
in CI (subscription auth isn't available there), so qualify it **locally**
|
|
31
|
+
before merging:
|
|
32
|
+
```
|
|
33
|
+
neal compat --model <a-known-good-slug> --role all
|
|
34
|
+
```
|
|
35
|
+
and ideally a one-fixture end-to-end run.
|
|
36
|
+
4. **Adopt.** Merge, bump neal's version, add a CHANGELOG entry noting the bump +
|
|
37
|
+
any behavior change, and cut a release via the existing workflow.
|
|
38
|
+
|
|
39
|
+
## Versioning
|
|
40
|
+
|
|
41
|
+
SemVer, treated as a **CLI/application** contract:
|
|
42
|
+
|
|
43
|
+
- **MAJOR** — breaking CLI/behavior: a command or flag removed, a plan-format or
|
|
44
|
+
config break, a provider-contract change users depend on.
|
|
45
|
+
- **MINOR** — new backward-compatible surface: a new command, flag, or provider.
|
|
46
|
+
- **PATCH** — bug fixes, no new surface.
|
|
47
|
+
|
|
48
|
+
Mapping a dependency bump to neal's version:
|
|
49
|
+
|
|
50
|
+
- Invisible to users (pin hygiene, internal) → **patch**.
|
|
51
|
+
- Adds a capability neal now exposes → **minor**.
|
|
52
|
+
- Changes observable behavior in a breaking way → **major** (or minor pre-1.0).
|
|
53
|
+
|
|
54
|
+
**Pre-1.0:** while on `0.x`, minor may break (the honest "still evolving"
|
|
55
|
+
contract). Move to **`1.0.0`** deliberately, once the CLI surface, plan format,
|
|
56
|
+
and provider contracts are stable enough to promise compatibility — not before.
|
|
57
|
+
|
|
58
|
+
## Changelog discipline
|
|
59
|
+
|
|
60
|
+
Every release updates [CHANGELOG.md](../CHANGELOG.md) (Keep a Changelog format).
|
|
61
|
+
Agentic-SDK bumps get an explicit line with the from→to versions and a one-line
|
|
62
|
+
behavior note ("re-qualified with `neal compat`, no behavior change", or the
|
|
63
|
+
specific change observed). The dependency churn *is* the risk surface, so it is
|
|
64
|
+
recorded, not buried.
|