@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/release.md
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
# Release
|
|
1
|
+
# Release process
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
which is triggered manually with `workflow_dispatch`.
|
|
3
|
+
Releases start from the manual GitHub Actions `Publish` workflow. The workflow
|
|
4
|
+
checks and stages the npm package, waits for a maintainer's npm approval, then
|
|
5
|
+
creates the matching GitHub tag and release.
|
|
7
6
|
|
|
8
|
-
## Versioning
|
|
7
|
+
## Versioning policy
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
neal uses Semantic Versioning. `package.json.version` changes only in an
|
|
11
10
|
intentional release-preparation pull request, not in ordinary implementation
|
|
12
11
|
commits. Release versions are exact SemVer strings without a leading `v`, and
|
|
13
12
|
an npm version must never be reused.
|
|
@@ -19,14 +18,14 @@ exit codes, or durable run artifacts require a major version.
|
|
|
19
18
|
|
|
20
19
|
Examples:
|
|
21
20
|
|
|
22
|
-
- Patch: `0.
|
|
21
|
+
- Patch: `0.3.2` fixes a defect or compatibility issue without intentionally
|
|
23
22
|
changing documented behavior.
|
|
24
|
-
- Minor before `1.0.0`: `0.
|
|
23
|
+
- Minor before `1.0.0`: `0.4.0` adds a feature or meaningful behavior change
|
|
25
24
|
while the public contract is still settling.
|
|
26
|
-
- Major: `1.0.0` establishes the stable public contract
|
|
25
|
+
- Major: `1.0.0` establishes the stable public contract. Later breaking
|
|
27
26
|
contract changes require `2.0.0`, `3.0.0`, and so on.
|
|
28
27
|
|
|
29
|
-
## SDK
|
|
28
|
+
## SDK dependency policy
|
|
30
29
|
|
|
31
30
|
Direct coding-agent and AI SDK runtime dependencies must use exact SemVer specs
|
|
32
31
|
in `package.json`. Do not use `latest`, caret ranges, tilde ranges, wildcards,
|
|
@@ -41,105 +40,86 @@ published CLI package:
|
|
|
41
40
|
|
|
42
41
|
An SDK update should land as a normal dependency pull request that updates both
|
|
43
42
|
`package.json` and `pnpm-lock.yaml`, runs normal CI, and records any provider
|
|
44
|
-
compatibility findings.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
compatibility findings. Match the neal release to the user-facing impact: use a
|
|
44
|
+
patch release for a compatibility fix that preserves documented behavior, a
|
|
45
|
+
minor release for behavior changes before `1.0.0`, and a major release after
|
|
46
|
+
`1.0.0` if a documented public contract breaks.
|
|
48
47
|
|
|
49
|
-
##
|
|
48
|
+
## Prepare a release
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
Bump `package.json.version` and add a nonempty `## [<version>]` section to
|
|
51
|
+
`CHANGELOG.md` in a normal release-preparation pull request:
|
|
53
52
|
|
|
54
53
|
```sh
|
|
55
54
|
pnpm version <version> --no-git-tag-version
|
|
56
55
|
```
|
|
57
56
|
|
|
58
|
-
Run the
|
|
57
|
+
Run the release gates from the repository root:
|
|
59
58
|
|
|
60
59
|
```sh
|
|
61
60
|
RELEASE_VERSION=<version> RELEASE_DRY_RUN=true pnpm run validate:release
|
|
62
61
|
pnpm typecheck
|
|
63
62
|
pnpm test
|
|
63
|
+
pnpm lint
|
|
64
64
|
pnpm build
|
|
65
65
|
node scripts/verify-package.mjs
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
`validate:release` checks
|
|
69
|
-
|
|
70
|
-
availability, and checks remote `v<version>` tag availability when an `origin`
|
|
71
|
-
remote is configured. Package verification confirms the built CLI and packed
|
|
72
|
-
tarball.
|
|
68
|
+
`validate:release` checks the package version, required metadata, direct SDK
|
|
69
|
+
pins, changelog section, npm version availability, and remote tag availability.
|
|
73
70
|
|
|
74
|
-
##
|
|
71
|
+
## Publish
|
|
75
72
|
|
|
76
|
-
After the release-preparation pull request is merged and normal CI passes,
|
|
77
|
-
|
|
73
|
+
After the release-preparation pull request is merged and normal CI passes, open
|
|
74
|
+
**Actions > Publish > Run workflow** on `main`.
|
|
78
75
|
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
Run it first with the exact version and `dry_run: true`. This runs every gate
|
|
77
|
+
and `npm publish --dry-run` without changing npm or GitHub.
|
|
81
78
|
|
|
82
|
-
|
|
83
|
-
environment, grants `contents: read` and `id-token: write`, installs with
|
|
84
|
-
`pnpm install --frozen-lockfile`, and runs these gates before any publish step:
|
|
79
|
+
Run it again with `dry_run: false` after reviewing the dry run. The workflow:
|
|
85
80
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
Review the dry-run result before any real publish. Run `Publish` again with
|
|
95
|
-
`dry_run: false` only when a release-preparation plan has explicitly authorized
|
|
96
|
-
the real publish.
|
|
97
|
-
|
|
98
|
-
The real-publish path is **staged**: the workflow runs `npm stage publish`,
|
|
99
|
-
which places the version in a staged, not-publicly-available state. A
|
|
100
|
-
maintainer then reviews and approves it with 2FA — `npm stage list`,
|
|
101
|
-
`npm stage view <stage-id>` / `npm stage download <stage-id>`, and
|
|
102
|
-
`npm stage approve <stage-id>` (or the npmjs.com UI). Nothing reaches `latest`
|
|
103
|
-
without that human approval, so a compromised workflow cannot ship directly.
|
|
104
|
-
|
|
105
|
-
## First-Publish History
|
|
81
|
+
1. Runs the same release gates.
|
|
82
|
+
2. Runs `npm stage publish` and writes the stage ID and approval commands to the
|
|
83
|
+
GitHub job summary.
|
|
84
|
+
3. Waits up to 60 minutes for the package to become public.
|
|
85
|
+
4. Verifies npm's signed provenance against the workflow's repository, path,
|
|
86
|
+
branch, and exact commit SHA.
|
|
87
|
+
5. Creates `v<version>` at that commit and a GitHub release from the matching
|
|
88
|
+
changelog section.
|
|
106
89
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
workflow and the staged flow above.
|
|
90
|
+
Review the stage with `npm stage view <stage-id>` or
|
|
91
|
+
`npm stage download <stage-id>`. Approve it with 2FA using
|
|
92
|
+
`npm stage approve <stage-id>` or the npmjs.com UI. npm won't publish the
|
|
93
|
+
package until that approval happens.
|
|
112
94
|
|
|
113
|
-
##
|
|
95
|
+
## Recovery
|
|
114
96
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
contents read-only, uses the `npm-publish` GitHub environment, and runs
|
|
118
|
-
`npm stage publish` only on the real-publish path (staged publishing requires
|
|
119
|
-
npm >= 11.15.0; the workflow upgrades npm accordingly).
|
|
97
|
+
Rerun `Publish` with the same version and `dry_run: false` after a timeout or a
|
|
98
|
+
partial failure. The workflow checks existing state before taking action:
|
|
120
99
|
|
|
121
|
-
|
|
100
|
+
- If the npm version isn't public, it stages it and waits for approval.
|
|
101
|
+
- If the npm version is public, it skips staging and verifies its signed
|
|
102
|
+
provenance against the current workflow commit.
|
|
103
|
+
- If the tag or GitHub release already exists at the expected commit, it keeps
|
|
104
|
+
it and finishes successfully.
|
|
105
|
+
- If the npm provenance or tag points to another commit, it stops without
|
|
106
|
+
changing the existing release.
|
|
122
107
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
unaffected because they use OIDC, and staged approvals always require a
|
|
126
|
-
maintainer's 2FA.
|
|
127
|
-
- Trusted publisher: owner/repo `navels/neal`, workflow filename
|
|
128
|
-
`publish.yml`, environment `npm-publish`, allowed action **`npm stage
|
|
129
|
-
publish` only** (stage-only; plain `npm publish` is deliberately not
|
|
130
|
-
granted).
|
|
108
|
+
If the approval wait times out, approve the existing npm stage before rerunning.
|
|
109
|
+
Do not try to stage the same version a second time.
|
|
131
110
|
|
|
132
|
-
##
|
|
111
|
+
## Trusted publishing setup
|
|
133
112
|
|
|
134
|
-
The
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
113
|
+
The workflow uses npm trusted publishing with no token fallback. The npm trusted
|
|
114
|
+
publisher is configured for owner/repository `navels/neal`, workflow
|
|
115
|
+
`publish.yml`, environment `npm-publish`, and the **`npm stage publish` only**
|
|
116
|
+
permission. Publishing access requires two-factor authentication and disallows
|
|
117
|
+
traditional tokens.
|
|
139
118
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
119
|
+
The workflow grants `id-token: write` for npm's OpenID Connect authentication
|
|
120
|
+
and `contents: write` for the final tag and GitHub release. The `npm-publish`
|
|
121
|
+
environment accepts only `main`. npm's stage-only permission means the workflow
|
|
122
|
+
can't make a package public without a maintainer's 2FA approval.
|
|
143
123
|
|
|
144
124
|
Official references:
|
|
145
125
|
|
|
@@ -147,5 +127,7 @@ Official references:
|
|
|
147
127
|
<https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow>
|
|
148
128
|
- npm trusted publishing:
|
|
149
129
|
<https://docs.npmjs.com/trusted-publishers/>
|
|
130
|
+
- npm staged packages:
|
|
131
|
+
<https://docs.npmjs.com/staged-publishing/>
|
|
150
132
|
- npm trust command:
|
|
151
133
|
<https://docs.npmjs.com/cli/v11/commands/npm-trust/>
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Plan-review convergence
|
|
2
|
+
|
|
3
|
+
Plan review either converges or the run fails. The reviewer is asked "is this
|
|
4
|
+
plan execution-ready?" fresh every round against a document that grows with each
|
|
5
|
+
fix, so every individually-defensible new finding forces another full revision
|
|
6
|
+
round. The only non-acceptance exits used to be terminal failures: reaching the
|
|
7
|
+
round cap, and an unattended coder block. That produced two failure modes in
|
|
8
|
+
real runs: long negotiations that keep re-litigating verification strength after
|
|
9
|
+
the plan is already correct, and runs that terminally fail when a coder
|
|
10
|
+
legitimately needs author input it must not fabricate.
|
|
11
|
+
|
|
12
|
+
The convergence policy addresses both failure modes as **deterministic
|
|
13
|
+
orchestration policy**, not prompt tuning. It never shortens a negotiation that
|
|
14
|
+
surfaced a genuine plan-correctness defect, and only stops burning rounds on
|
|
15
|
+
verification-hardening demands once the plan itself is correct. And a
|
|
16
|
+
coder-authored plan-stage block no longer terminally fails: it lands as a
|
|
17
|
+
recoverable blocked-with-reason state that an operator can answer via
|
|
18
|
+
`neal resume --message` (see
|
|
19
|
+
[Coder-authored plan-stage block recovery](#coder-authored-plan-stage-block-recovery)).
|
|
20
|
+
|
|
21
|
+
The core round policy is a single pure function,
|
|
22
|
+
`synthesizePlanReviewRoundFromFindings` in `src/neal/adjudicator/planning.ts`,
|
|
23
|
+
shared by the runtime (`runPlanReviewPhase`) and by the fixture replay harness
|
|
24
|
+
under `test/fixtures/plan-review-ledgers/` (see that directory's `SCHEMA.md`), so
|
|
25
|
+
the policy pinned by the committed ledgers is exactly the policy that runs.
|
|
26
|
+
|
|
27
|
+
## Finding classes
|
|
28
|
+
|
|
29
|
+
Every plan-review finding carries a declared `findingClass`:
|
|
30
|
+
|
|
31
|
+
- **`plan_correctness`**: the plan would build the wrong thing, build an
|
|
32
|
+
unverifiable thing, or contains an impossible or self-contradictory
|
|
33
|
+
instruction. These always force a revision round (or block at the cap). The
|
|
34
|
+
auto-generated plan-structure findings default to `plan_correctness`.
|
|
35
|
+
- **`verification_hardening`**: the finding only demands strengthening *how the
|
|
36
|
+
plan will be verified* (more oracles, more pinning, more coverage of
|
|
37
|
+
already-specified behavior) without asserting the plan builds the wrong or an
|
|
38
|
+
unverifiable thing. Past the round threshold these can convert to recorded
|
|
39
|
+
debt instead of forcing another round.
|
|
40
|
+
|
|
41
|
+
The class has two deliberately different boundaries:
|
|
42
|
+
|
|
43
|
+
- **Reviewer-payload boundary (strict).** An *absent* class normalizes to
|
|
44
|
+
`plan_correctness`. A *present-but-invalid* class value is rejected exactly as
|
|
45
|
+
an invalid `severity` is: the sequential payload validator throws
|
|
46
|
+
`must be exactly one of: plan_correctness, verification_hardening`, so an
|
|
47
|
+
invalid class is a validation error, never a silent downgrade.
|
|
48
|
+
- **Persisted-state boundary (tolerant).** When hydrating a saved run,
|
|
49
|
+
`hydrateFinding` maps a present-but-unknown class to `plan_correctness` and
|
|
50
|
+
leaves an absent class `undefined` (execute-review findings never carry a
|
|
51
|
+
class and must stay unaffected). Older run states load without error.
|
|
52
|
+
|
|
53
|
+
The system-wide fail-safe does **not** depend on hydration filling absence: the
|
|
54
|
+
decision sites convert only an *explicit* `verification_hardening`, so any
|
|
55
|
+
absent/`undefined`/unknown class is always round-forcing (blocking).
|
|
56
|
+
|
|
57
|
+
## The debt round threshold
|
|
58
|
+
|
|
59
|
+
The knob `neal.plan_review_debt_round_threshold` (default **3**, read via
|
|
60
|
+
`getPlanReviewDebtRoundThreshold(cwd)`) is the reviewer round at or past which a
|
|
61
|
+
novel verification-hardening finding may convert to debt instead of forcing a
|
|
62
|
+
round. Below the threshold, every blocking finding forces a revision round
|
|
63
|
+
regardless of class. Early rounds are for genuine convergence, not banking.
|
|
64
|
+
|
|
65
|
+
## Disposition policy
|
|
66
|
+
|
|
67
|
+
For each round the policy partitions the merged open blocking findings
|
|
68
|
+
(`classifyPlanReviewConvergence`) into `debtConvertible` and `roundForcing`:
|
|
69
|
+
|
|
70
|
+
Pre-cap, a finding is `debtConvertible` only when **all** of:
|
|
71
|
+
|
|
72
|
+
- its class is exactly `verification_hardening`
|
|
73
|
+
- it is a **first occurrence** of its canonical (the minimum round across all
|
|
74
|
+
merged findings sharing that canonical equals the current round)
|
|
75
|
+
- the current round is `>= threshold`.
|
|
76
|
+
|
|
77
|
+
Everything else is `roundForcing`: any `plan_correctness`/absent/unknown class,
|
|
78
|
+
**any repeat occurrence** of an existing canonical (a re-raised point still earns
|
|
79
|
+
a dedicated round pre-cap), or any below-threshold round.
|
|
80
|
+
|
|
81
|
+
From that partition:
|
|
82
|
+
|
|
83
|
+
- **Arrival-time conversion.** A `debtConvertible` finding banks as `deferred`
|
|
84
|
+
plan-review debt the moment it arrives, even when a co-occurring
|
|
85
|
+
`plan_correctness` or repeat finding forces the round, so novelty-bounded
|
|
86
|
+
hardening asks never extend the round count.
|
|
87
|
+
- **Accept-with-recorded-reservations landing.** When every open blocking
|
|
88
|
+
finding is `debtConvertible` and none is `roundForcing`, the round lands
|
|
89
|
+
accepted (`landAcceptedWithDebt`). It reuses the existing acceptance
|
|
90
|
+
transition (`accept_plan` → `done` for top-level plan review,
|
|
91
|
+
`accept_derived_plan` for a derived-plan review), so no adjudication-spec
|
|
92
|
+
outcome is added.
|
|
93
|
+
|
|
94
|
+
Branch precedence in `resolvePlanReviewDisposition` is strict:
|
|
95
|
+
|
|
96
|
+
1. convergence block (reopen / stall) →
|
|
97
|
+
2. round-forcing blocking findings →
|
|
98
|
+
3. accept-with-debt →
|
|
99
|
+
4. open non-blocking findings →
|
|
100
|
+
5. clean acceptance.
|
|
101
|
+
|
|
102
|
+
Because `roundForcing` outranks `landAcceptedWithDebt`, a single open
|
|
103
|
+
plan-correctness or repeat blocker keeps the round forced even when convertible
|
|
104
|
+
hardening findings are present alongside it.
|
|
105
|
+
|
|
106
|
+
## Cap behavior
|
|
107
|
+
|
|
108
|
+
At the round cap (`round >= roundLimit`) there are no rounds left to force, so the
|
|
109
|
+
convertible predicate becomes **class-only**: *every* open
|
|
110
|
+
`verification_hardening` finding converts regardless of first-occurrence or
|
|
111
|
+
threshold. The cap contract:
|
|
112
|
+
|
|
113
|
+
- If the convergence block did **not** fire and *every* open blocking finding is
|
|
114
|
+
`verification_hardening`, the run lands accepted-with-debt (all convert).
|
|
115
|
+
- If any open blocking finding is `plan_correctness` (or absent/unknown class),
|
|
116
|
+
the run terminal-blocks with the existing max-rounds reason.
|
|
117
|
+
|
|
118
|
+
The convergence block keeps precedence at the cap too: a re-opened
|
|
119
|
+
(`getReopenedCanonical`, ≥3 blocking rounds of one canonical) or stalled
|
|
120
|
+
(`hasRepeatedUnresolvedBlockingCanonicals`) finding, *even hardening-class*,
|
|
121
|
+
terminal-blocks rather than converting. So a re-litigated-but-not-reopened
|
|
122
|
+
hardening point converts at the cap, but a genuinely stuck one still blocks.
|
|
123
|
+
|
|
124
|
+
## Plan-review debt: two fields, two lifecycles
|
|
125
|
+
|
|
126
|
+
Debt is modeled as a **canonical-keyed projection of the current findings**
|
|
127
|
+
(`toPlanReviewDebt`), never an accumulator: for each canonical whose
|
|
128
|
+
latest-round finding is a `deferred` `verification_hardening` finding, exactly
|
|
129
|
+
one debt item is emitted (latest round wins, `originRound` = that finding's
|
|
130
|
+
round). A banked canonical that later reopens, is fixed, or is rejected simply
|
|
131
|
+
drops out of the projection. The lifecycle is automatic, with no stale or
|
|
132
|
+
duplicate entries and no ad-hoc removal rule.
|
|
133
|
+
|
|
134
|
+
There are two distinct top-level state fields:
|
|
135
|
+
|
|
136
|
+
- **`planReviewDebt`**: the **current-negotiation** projection. Recomputed as
|
|
137
|
+
`toPlanReviewDebt(mergedFindings)` everywhere the current findings change
|
|
138
|
+
(`synthesizePlanReviewRound` / `runPlanReviewPhase` and
|
|
139
|
+
`runPlanningResponsePhase`). This is what a top-level plan run carries to the
|
|
140
|
+
queue on completion.
|
|
141
|
+
- **`inheritedPlanReviewDebt`**: **durable, write-once**. Seeded only at init
|
|
142
|
+
from the queue handoff and **never recomputed by any plan-review phase**.
|
|
143
|
+
|
|
144
|
+
Two fields are required because an *execution* child re-enters plan review: the
|
|
145
|
+
execute runnable-phase registry includes `reviewer_plan`,
|
|
146
|
+
`coder_plan_response`, and `coder_plan_optional_response`, so
|
|
147
|
+
`runPlanReviewPhase` runs inside the execution child during split-plan recovery.
|
|
148
|
+
If a single recomputed field were both seeded from the inherited debt and
|
|
149
|
+
recomputed as `toPlanReviewDebt(mergedFindings)`, that derived-plan review would
|
|
150
|
+
project only the derived plan's own findings and **erase** the inherited
|
|
151
|
+
top-level debt before the next scope reviewer saw it. Keeping
|
|
152
|
+
`inheritedPlanReviewDebt` write-once and untouched by any plan-review phase
|
|
153
|
+
guarantees a derived-plan review in the execution child can only ever mutate
|
|
154
|
+
`planReviewDebt`, never the inherited debt the execution reviewer actually needs.
|
|
155
|
+
|
|
156
|
+
## Cross-run handoff (and its exclusion)
|
|
157
|
+
|
|
158
|
+
Plan→execution handoff is **cross-run, not in-process**: `neal run` runs planning
|
|
159
|
+
and execution as separate fresh child runs. The durable carrier is the
|
|
160
|
+
`PlanAndExecuteQueueItem`:
|
|
161
|
+
|
|
162
|
+
1. On planning-child completion, `completePlanningStage` copies
|
|
163
|
+
`finalState.planReviewDebt` onto the queue item alongside `acceptedPlanPath`
|
|
164
|
+
(covering both in-process completion and the cross-process resume through
|
|
165
|
+
`continuePlanAndExecuteQueueFromChildRun`).
|
|
166
|
+
2. When the fresh execution child starts, `runFreshPlanAndExecuteChild` passes
|
|
167
|
+
the item's debt as the `inheritedPlanReviewDebt` init option **only** for the
|
|
168
|
+
execution stage. `createInitialState` seeds the durable
|
|
169
|
+
`inheritedPlanReviewDebt` state field from it (the recomputed `planReviewDebt`
|
|
170
|
+
always starts empty).
|
|
171
|
+
3. `buildReviewerContextPacket` surfaces the inherited debt to the execution
|
|
172
|
+
reviewer as full items (`canonicalId`, `findingClass`, `originRound`,
|
|
173
|
+
`claim`, `requiredAction`) under a dedicated `## Inherited Plan-Review Debt`
|
|
174
|
+
section, not a bare count.
|
|
175
|
+
|
|
176
|
+
**Exclusion (by design):** cross-run propagation is supported only inside a
|
|
177
|
+
single `neal run` queue (planning child → execution child). A standalone
|
|
178
|
+
`neal plan` run persists debt only in its own run record. A later
|
|
179
|
+
`neal execute`/`neal run` against the accepted plan is a fresh run that
|
|
180
|
+
re-reviews the plan and does **not** inherit that debt.
|
|
181
|
+
|
|
182
|
+
## Where debt is visible
|
|
183
|
+
|
|
184
|
+
- **`REVIEW.md`** (`renderReviewMarkdown`): each finding line carries
|
|
185
|
+
`- Finding class:` (with `n/a` for classless execute-review findings), and a
|
|
186
|
+
dedicated `## Plan Review Debt` section lists `Inherited:` and `Current:`
|
|
187
|
+
groups.
|
|
188
|
+
- **`neal status`** (`buildStatusSnapshot` / `renderHumanStatusSnapshot`): a
|
|
189
|
+
`planReviewDebt` snapshot field sums both arrays with a per-item `inherited`
|
|
190
|
+
flag, rendered as a `Plan review debt:` line.
|
|
191
|
+
- **Reviewer context packet**: the `## Inherited Plan-Review Debt` section
|
|
192
|
+
described above.
|
|
193
|
+
|
|
194
|
+
## Coder-authored plan-stage block recovery
|
|
195
|
+
|
|
196
|
+
The other non-acceptance exit is a plan-stage coder block. A coder-authored
|
|
197
|
+
plan-stage **response** block (a `coder_plan_response` or
|
|
198
|
+
`coder_plan_optional_response` block, the only plan-stage path that previously
|
|
199
|
+
terminal-failed) lands as the documented blocked contract instead of a terminal
|
|
200
|
+
failure. `finalizeBlockedPlanReviewResponse`
|
|
201
|
+
(`src/neal/orchestrator/phases/planning.ts`) takes an explicit `blockCause`
|
|
202
|
+
(`coder_authored` | `dirty_worktree` | `reviewer_convergence`), and for a
|
|
203
|
+
`coder_authored` block on the top-level plan stage, in **both attended and
|
|
204
|
+
unattended runs**, it:
|
|
205
|
+
|
|
206
|
+
- persists `status: 'blocked'` with a durable `blockerReason` (the coder's
|
|
207
|
+
reported blocker), so the writer exits **2** (not `failed`/exit 3) and the
|
|
208
|
+
reason surfaces in both the JSON and human `neal status` renderings.
|
|
209
|
+
- makes the block answerable via `neal resume --message`, reusing the existing
|
|
210
|
+
`pendingPlanReviewGuidance` channel and `runPlanningResponseAdjudication`
|
|
211
|
+
delivery. `recordPlanReviewGuidance`
|
|
212
|
+
(`src/neal/commands/recovery-guidance.ts`) maps the origin to the phase that
|
|
213
|
+
consumes the guidance: a `reviewer_plan` block still delivers to
|
|
214
|
+
`coder_plan_response` (unchanged), while a coder-authored response block returns
|
|
215
|
+
to its own origin phase (a `coder_plan_optional_response` block returns to
|
|
216
|
+
`coder_plan_optional_response`), so the re-run selects the right open findings
|
|
217
|
+
and delivers the guidance instead of accepting on an empty blocking set.
|
|
218
|
+
Recorded guidance forces a response adjudication even when a prior blocked
|
|
219
|
+
response closed every finding, so the operator's answer is never silently
|
|
220
|
+
discarded.
|
|
221
|
+
|
|
222
|
+
The invariant is that `blockerReason` is `null` whenever `status !== 'blocked'`.
|
|
223
|
+
The resume-planner blocked→running transitions clear it so it never outlives its
|
|
224
|
+
block.
|
|
225
|
+
|
|
226
|
+
Only `coder_authored` response blocks take this recoverable landing, and the
|
|
227
|
+
durable `blockerReason` is the discriminator. A `dirty_worktree` safety block
|
|
228
|
+
(the planner dirtied non-plan files with no operator to clean them) records no
|
|
229
|
+
`blockerReason`: it lands at the same response phase but stays a normal blocked
|
|
230
|
+
state: it is not reported or answerable as waiting for `--message` guidance, and
|
|
231
|
+
it keeps its prior bare-resume behavior when a resumable planner session exists. A
|
|
232
|
+
`dirty_worktree` safety block and reviewer cap/stall exhaustion (`reviewer_convergence`)
|
|
233
|
+
under `unattended` keep terminal-failing (exit 3).
|
|
234
|
+
|
|
235
|
+
**Exclusion (the initial `coder_plan` authoring block):** the author-input route
|
|
236
|
+
does **not** cover the initial `coder_plan` block. That block already lands
|
|
237
|
+
`status: 'blocked'` (exit 2, not a terminal failure) and stays resumable via a
|
|
238
|
+
bare `neal resume`. Delivering an operator answer to a re-run authoring round
|
|
239
|
+
would require a second guidance-delivery channel this policy deliberately avoids.
|
|
240
|
+
Its `--message` route is a deliberate follow-up, not part of this policy.
|
|
241
|
+
|
|
242
|
+
## Related references
|
|
243
|
+
|
|
244
|
+
- `docs/plan-format.md`: executable plan shapes and normalization.
|
|
245
|
+
- `docs/state-machine.md`: persisted run and queue state invariants.
|
|
246
|
+
- `docs/adjudicator-inventory.md`: the shared coder/reviewer loop inventory.
|