@navels/neal 0.1.0 → 0.3.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/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 +264 -24
- package/dist/neal/agents/prompts.js +7 -15
- package/dist/neal/agents/rounds.js +51 -73
- package/dist/neal/agents/schemas.js +615 -502
- package/dist/neal/agents/structured-json.js +36 -0
- 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 +40 -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/git.js +9 -3
- package/dist/neal/interactive-controls.js +5 -0
- package/dist/neal/orchestrator/completion.js +215 -113
- package/dist/neal/orchestrator/failures.js +7 -0
- package/dist/neal/orchestrator/notifications.js +9 -9
- package/dist/neal/orchestrator/phases/planning.js +124 -112
- package/dist/neal/orchestrator/phases/recovery.js +151 -115
- package/dist/neal/orchestrator/phases/review.js +28 -1
- package/dist/neal/orchestrator/split-plan.js +12 -11
- package/dist/neal/orchestrator/transitions.js +36 -71
- package/dist/neal/orchestrator.js +3 -0
- package/dist/neal/plan-doc.js +24 -1
- package/dist/neal/plan-queue.js +29 -1
- package/dist/neal/prompts/assert-builder.js +8 -1
- package/dist/neal/prompts/execute.js +11 -19
- package/dist/neal/prompts/planning.js +64 -55
- package/dist/neal/prompts/review-doctrine.js +13 -50
- package/dist/neal/prompts/specialized.js +28 -22
- package/dist/neal/prompts/specs.js +223 -17
- package/dist/neal/providers/agent-settings-isolation.js +35 -0
- package/dist/neal/providers/anthropic-claude.js +309 -248
- 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 +102 -204
- package/dist/neal/providers/{generic-agentic-tools.js → openai-compatible-tools.js} +11 -5
- package/dist/neal/providers/openai-compatible.js +1079 -403
- package/dist/neal/providers/openrouter-routing.js +68 -0
- package/dist/neal/providers/pricing.js +124 -0
- package/dist/neal/providers/rate-card.js +2275 -0
- package/dist/neal/providers/registry.js +6 -17
- package/dist/neal/providers/telemetry.js +4 -0
- 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 +36 -7
- 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/run-metrics.js +74 -9
- 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 -110
- package/docs/demo.md +15 -5
- package/docs/maintenance.md +74 -32
- 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 +330 -283
- package/docs/release.md +43 -40
- 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 +20 -15
- package/dist/neal/providers/generic-agentic.js +0 -1238
- package/docs/comparison.md +0 -105
- 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/dist/neal/state.js
CHANGED
|
@@ -85,7 +85,7 @@ export async function createInitialState(init, baseCommit) {
|
|
|
85
85
|
agentConfig: init.agentConfig,
|
|
86
86
|
unattended: init.unattended ?? false,
|
|
87
87
|
unattendedAutoResumeCount: 0,
|
|
88
|
-
|
|
88
|
+
consultantAttemptCount: 0,
|
|
89
89
|
autoSquashOnCompletion: init.autoSquashOnCompletion ?? true,
|
|
90
90
|
progressJsonPath: init.progressJsonPath,
|
|
91
91
|
progressMarkdownPath: init.progressMarkdownPath,
|
|
@@ -136,6 +136,14 @@ export async function createInitialState(init, baseCommit) {
|
|
|
136
136
|
interactiveBlockedRecovery: null,
|
|
137
137
|
interactiveBlockedRecoveryHistory: [],
|
|
138
138
|
pendingPlanReviewGuidance: null,
|
|
139
|
+
// Current-negotiation plan-review debt always starts empty; it is a
|
|
140
|
+
// projection of the current findings recomputed as review rounds run.
|
|
141
|
+
planReviewDebt: [],
|
|
142
|
+
// Durable inherited plan-review debt is seeded once from the queue handoff
|
|
143
|
+
// (execution stage) and never recomputed; default empty for every other run.
|
|
144
|
+
inheritedPlanReviewDebt: [...(init.inheritedPlanReviewDebt ?? [])],
|
|
145
|
+
// A fresh run starts running, so the blocked-reason invariant requires null.
|
|
146
|
+
blockerReason: null,
|
|
139
147
|
status: 'running',
|
|
140
148
|
};
|
|
141
149
|
}
|
|
@@ -189,6 +197,7 @@ const REVIEWER_MEANINGFUL_PROGRESS_ACTIONS = ['accept', 'block_for_operator', 'r
|
|
|
189
197
|
const REVIEW_FINDING_SOURCES = ['reviewer', 'plan_structure'];
|
|
190
198
|
const REVIEW_FINDING_SEVERITIES = ['blocking', 'non_blocking'];
|
|
191
199
|
const REVIEW_FINDING_STATUSES = ['open', 'fixed', 'rejected', 'deferred'];
|
|
200
|
+
const PLAN_REVIEWER_FINDING_CLASSES = ['plan_correctness', 'verification_hardening'];
|
|
192
201
|
const MANUAL_GATE_CHECK_TYPES = ['command'];
|
|
193
202
|
const MANUAL_GATE_CHECK_CWDS = ['repo', 'run_dir'];
|
|
194
203
|
const MANUAL_GATE_RESUME_PHASES = ['coder_scope'];
|
|
@@ -592,15 +601,30 @@ function hydrateRecentBlock(value, fieldPath = 'recentBlocks[]') {
|
|
|
592
601
|
recordedAt: readString(record, 'recordedAt', `${fieldPath}.recordedAt`),
|
|
593
602
|
};
|
|
594
603
|
}
|
|
604
|
+
// Tolerant read for the plan-review finding class: an absent key stays
|
|
605
|
+
// undefined (execute-review findings and legacy states never set it and must be
|
|
606
|
+
// unaffected), a recognized class is kept, and any other present value is
|
|
607
|
+
// coerced to the fail-safe plan_correctness rather than throwing like readEnum.
|
|
608
|
+
function readTolerantFindingClass(record, key) {
|
|
609
|
+
if (!hasOwn(record, key)) {
|
|
610
|
+
return undefined;
|
|
611
|
+
}
|
|
612
|
+
const value = record[key];
|
|
613
|
+
return typeof value === 'string' && PLAN_REVIEWER_FINDING_CLASSES.includes(value)
|
|
614
|
+
? value
|
|
615
|
+
: 'plan_correctness';
|
|
616
|
+
}
|
|
595
617
|
function hydrateFinding(value, fieldPath = 'findings[]') {
|
|
596
618
|
const finding = requireRecord(value, fieldPath);
|
|
597
619
|
const evidence = readOptionalNullableString(finding, 'evidence', `${fieldPath}.evidence`);
|
|
620
|
+
const findingClass = readTolerantFindingClass(finding, 'findingClass');
|
|
598
621
|
return {
|
|
599
622
|
id: readString(finding, 'id', `${fieldPath}.id`),
|
|
600
623
|
canonicalId: readString(finding, 'canonicalId', `${fieldPath}.canonicalId`),
|
|
601
624
|
round: readSafeInteger(finding, 'round', `${fieldPath}.round`),
|
|
602
625
|
source: readEnum(finding, 'source', REVIEW_FINDING_SOURCES, `${fieldPath}.source`),
|
|
603
626
|
severity: readEnum(finding, 'severity', REVIEW_FINDING_SEVERITIES, `${fieldPath}.severity`),
|
|
627
|
+
...(findingClass !== undefined ? { findingClass } : {}),
|
|
604
628
|
files: readStringArray(finding, 'files', `${fieldPath}.files`),
|
|
605
629
|
claim: readString(finding, 'claim', `${fieldPath}.claim`),
|
|
606
630
|
...(evidence !== undefined ? { evidence } : {}),
|
|
@@ -647,23 +671,23 @@ function hydrateInteractiveBlockedRecoveryDirective(value, fieldPath) {
|
|
|
647
671
|
terminalOnly: readBoolean(directive, 'terminalOnly', `${fieldPath}.terminalOnly`),
|
|
648
672
|
};
|
|
649
673
|
}
|
|
650
|
-
const
|
|
674
|
+
const CONSULTANT_TRIAGE_CATEGORIES = [
|
|
651
675
|
'misunderstanding',
|
|
652
676
|
'authorization',
|
|
653
677
|
'external_precondition',
|
|
654
678
|
'impossible_task',
|
|
655
679
|
];
|
|
656
|
-
function
|
|
657
|
-
const advice = readOptionalNullableRecord(record, '
|
|
680
|
+
function hydrateInteractiveBlockedRecoveryConsultantAdvice(record, fieldPath) {
|
|
681
|
+
const advice = readOptionalNullableRecord(record, 'consultantAdvice', `${fieldPath}.consultantAdvice`);
|
|
658
682
|
if (!advice) {
|
|
659
683
|
return undefined;
|
|
660
684
|
}
|
|
661
685
|
return {
|
|
662
|
-
recordedAt: readString(advice, 'recordedAt', `${fieldPath}.
|
|
663
|
-
recoverable: readBoolean(advice, 'recoverable', `${fieldPath}.
|
|
664
|
-
triageCategory: readEnum(advice, 'triageCategory',
|
|
665
|
-
resolutionDirective: readString(advice, 'resolutionDirective', `${fieldPath}.
|
|
666
|
-
rationale: readString(advice, 'rationale', `${fieldPath}.
|
|
686
|
+
recordedAt: readString(advice, 'recordedAt', `${fieldPath}.consultantAdvice.recordedAt`),
|
|
687
|
+
recoverable: readBoolean(advice, 'recoverable', `${fieldPath}.consultantAdvice.recoverable`),
|
|
688
|
+
triageCategory: readEnum(advice, 'triageCategory', CONSULTANT_TRIAGE_CATEGORIES, `${fieldPath}.consultantAdvice.triageCategory`),
|
|
689
|
+
resolutionDirective: readString(advice, 'resolutionDirective', `${fieldPath}.consultantAdvice.resolutionDirective`),
|
|
690
|
+
rationale: readString(advice, 'rationale', `${fieldPath}.consultantAdvice.rationale`),
|
|
667
691
|
};
|
|
668
692
|
}
|
|
669
693
|
function hydrateInteractiveBlockedRecoveryFields(record, fieldPath) {
|
|
@@ -671,7 +695,7 @@ function hydrateInteractiveBlockedRecoveryFields(record, fieldPath) {
|
|
|
671
695
|
if (!INTERACTIVE_BLOCKED_RECOVERY_SOURCE_PHASES.has(sourcePhase)) {
|
|
672
696
|
throwInvalidState(`${fieldPath}.sourcePhase`, `unsupported interactive blocked recovery source phase ${JSON.stringify(sourcePhase)}`);
|
|
673
697
|
}
|
|
674
|
-
const advice =
|
|
698
|
+
const advice = hydrateInteractiveBlockedRecoveryConsultantAdvice(record, fieldPath);
|
|
675
699
|
return {
|
|
676
700
|
enteredAt: readString(record, 'enteredAt', `${fieldPath}.enteredAt`),
|
|
677
701
|
sourcePhase: sourcePhase,
|
|
@@ -681,9 +705,9 @@ function hydrateInteractiveBlockedRecoveryFields(record, fieldPath) {
|
|
|
681
705
|
pendingDirective: hydrateInteractiveBlockedRecoveryDirective(readRequired(record, 'pendingDirective', `${fieldPath}.pendingDirective`), `${fieldPath}.pendingDirective`),
|
|
682
706
|
turns: readArray(record, 'turns', `${fieldPath}.turns`).map((turn, index) => hydrateInteractiveBlockedRecoveryTurn(turn, `${fieldPath}.turns[${index}]`)),
|
|
683
707
|
// Only attach the key when persisted advice exists, so a recovery record with
|
|
684
|
-
// no
|
|
708
|
+
// no consultant advice round-trips byte-for-byte (no `consultantAdvice:
|
|
685
709
|
// undefined` residue).
|
|
686
|
-
...(advice ? {
|
|
710
|
+
...(advice ? { consultantAdvice: advice } : {}),
|
|
687
711
|
};
|
|
688
712
|
}
|
|
689
713
|
function hydrateInteractiveBlockedRecovery(value, fieldPath = 'interactiveBlockedRecovery') {
|
|
@@ -712,7 +736,7 @@ function hydratePendingPlanReviewGuidance(record, fieldPath = 'pendingPlanReview
|
|
|
712
736
|
const guidance = requireRecord(value, fieldPath);
|
|
713
737
|
return {
|
|
714
738
|
message: readString(guidance, 'message', `${fieldPath}.message`),
|
|
715
|
-
sourcePhase: readEnum(guidance, 'sourcePhase', ['reviewer_plan'], `${fieldPath}.sourcePhase`),
|
|
739
|
+
sourcePhase: readEnum(guidance, 'sourcePhase', ['reviewer_plan', 'coder_plan_response', 'coder_plan_optional_response'], `${fieldPath}.sourcePhase`),
|
|
716
740
|
recordedAt: readString(guidance, 'recordedAt', `${fieldPath}.recordedAt`),
|
|
717
741
|
};
|
|
718
742
|
}
|
|
@@ -742,8 +766,13 @@ function hydrateRound(value, fieldPath = 'rounds[]') {
|
|
|
742
766
|
findings: readStringArray(round, 'findings', `${fieldPath}.findings`),
|
|
743
767
|
};
|
|
744
768
|
}
|
|
745
|
-
function hydrateResidualReviewDebtItem(value, fieldPath) {
|
|
769
|
+
export function hydrateResidualReviewDebtItem(value, fieldPath) {
|
|
746
770
|
const item = requireRecord(value, fieldPath);
|
|
771
|
+
// findingClass/originRound are declared only for plan-review debt; legacy
|
|
772
|
+
// execute-phase debt carries neither, so both stay optional and undefined when
|
|
773
|
+
// absent. findingClass reuses the tolerant reader (unknown → plan_correctness).
|
|
774
|
+
const findingClass = readTolerantFindingClass(item, 'findingClass');
|
|
775
|
+
const originRound = readOptionalSafeInteger(item, 'originRound', `${fieldPath}.originRound`);
|
|
747
776
|
return {
|
|
748
777
|
id: readString(item, 'id', `${fieldPath}.id`),
|
|
749
778
|
canonicalId: readString(item, 'canonicalId', `${fieldPath}.canonicalId`),
|
|
@@ -754,6 +783,8 @@ function hydrateResidualReviewDebtItem(value, fieldPath) {
|
|
|
754
783
|
requiredAction: readString(item, 'requiredAction', `${fieldPath}.requiredAction`),
|
|
755
784
|
coderDisposition: readNullableString(item, 'coderDisposition', `${fieldPath}.coderDisposition`),
|
|
756
785
|
coderCommit: readNullableString(item, 'coderCommit', `${fieldPath}.coderCommit`),
|
|
786
|
+
...(findingClass !== undefined ? { findingClass } : {}),
|
|
787
|
+
...(originRound !== undefined ? { originRound } : {}),
|
|
757
788
|
};
|
|
758
789
|
}
|
|
759
790
|
function hydrateCompletedScope(value, fieldPath = 'completedScopes[]') {
|
|
@@ -819,7 +850,7 @@ function normalizeStateV1(parsed) {
|
|
|
819
850
|
agentConfig: hydrateAgentConfig(readRequired(state, 'agentConfig'), 'agentConfig'),
|
|
820
851
|
unattended: readOptionalBoolean(state, 'unattended') ?? false,
|
|
821
852
|
unattendedAutoResumeCount: readOptionalSafeInteger(state, 'unattendedAutoResumeCount') ?? 0,
|
|
822
|
-
|
|
853
|
+
consultantAttemptCount: readOptionalSafeInteger(state, 'consultantAttemptCount') ?? 0,
|
|
823
854
|
// Legacy-tolerant: states persisted before this field existed hydrate to
|
|
824
855
|
// true, preserving the historical always-squash behavior on resume.
|
|
825
856
|
autoSquashOnCompletion: readOptionalBoolean(state, 'autoSquashOnCompletion') ?? true,
|
|
@@ -875,9 +906,22 @@ function normalizeStateV1(parsed) {
|
|
|
875
906
|
interactiveBlockedRecovery: hydrateInteractiveBlockedRecovery(readRequired(state, 'interactiveBlockedRecovery')),
|
|
876
907
|
interactiveBlockedRecoveryHistory: readArray(state, 'interactiveBlockedRecoveryHistory').map((record, index) => hydrateInteractiveBlockedRecoveryRecord(record, `interactiveBlockedRecoveryHistory[${index}]`)),
|
|
877
908
|
pendingPlanReviewGuidance: hydratePendingPlanReviewGuidance(state),
|
|
909
|
+
// Both plan-review debt arrays are legacy-tolerant: run states persisted
|
|
910
|
+
// before these fields existed hydrate to an empty array.
|
|
911
|
+
planReviewDebt: hydratePlanReviewDebtArray(state, 'planReviewDebt'),
|
|
912
|
+
inheritedPlanReviewDebt: hydratePlanReviewDebtArray(state, 'inheritedPlanReviewDebt'),
|
|
913
|
+
// Legacy-tolerant: run states persisted before this field existed hydrate to
|
|
914
|
+
// null. The blocked-reason invariant is enforced in assertValidOrchestrationState.
|
|
915
|
+
blockerReason: readOptionalNullableString(state, 'blockerReason') ?? null,
|
|
878
916
|
status: readEnum(state, 'status', ['running', 'paused', 'done', 'blocked', 'failed']),
|
|
879
917
|
};
|
|
880
918
|
}
|
|
919
|
+
function hydratePlanReviewDebtArray(state, key) {
|
|
920
|
+
if (!hasOwn(state, key)) {
|
|
921
|
+
return [];
|
|
922
|
+
}
|
|
923
|
+
return readArray(state, key).map((item, index) => hydrateResidualReviewDebtItem(item, `${key}[${index}]`));
|
|
924
|
+
}
|
|
881
925
|
export async function loadState(path) {
|
|
882
926
|
const content = await readFile(path, 'utf8');
|
|
883
927
|
const parsed = JSON.parse(content);
|
package/dist/neal/status.js
CHANGED
|
@@ -24,6 +24,7 @@ const PROVIDER_ERROR_KINDS = new Set([
|
|
|
24
24
|
'structured_output_invalid',
|
|
25
25
|
'permission_denied',
|
|
26
26
|
'session_unavailable',
|
|
27
|
+
'content_refused',
|
|
27
28
|
'provider_failed',
|
|
28
29
|
'unknown',
|
|
29
30
|
]);
|
|
@@ -157,6 +158,7 @@ export async function buildStatusSnapshot(args) {
|
|
|
157
158
|
lock,
|
|
158
159
|
health,
|
|
159
160
|
findings: summarizeFindings(state),
|
|
161
|
+
planReviewDebt: summarizePlanReviewDebt(state),
|
|
160
162
|
commits,
|
|
161
163
|
squash,
|
|
162
164
|
providerError,
|
|
@@ -245,7 +247,7 @@ export function renderHumanStatusSnapshot(snapshot) {
|
|
|
245
247
|
if (shouldShowScope(snapshot)) {
|
|
246
248
|
lines.push(`- Current scope: ${snapshot.currentScopeNumber}`);
|
|
247
249
|
}
|
|
248
|
-
lines.push(`- Health: ${snapshot.health.classification} - ${snapshot.health.reason}`, `- Findings: ${snapshot.findings.openBlocking} open blocking, ${snapshot.findings.openNonBlocking} open non-blocking (${snapshot.findings.total} total)`, `- Operator guidance: waiting=${formatBoolean(snapshot.waitingForOperatorGuidance)}, pending=${formatBoolean(snapshot.pendingOperatorGuidance)}`);
|
|
250
|
+
lines.push(`- Health: ${snapshot.health.classification} - ${snapshot.health.reason}`, `- Findings: ${snapshot.findings.openBlocking} open blocking, ${snapshot.findings.openNonBlocking} open non-blocking (${snapshot.findings.total} total)`, `- Plan review debt: ${formatPlanReviewDebtLine(snapshot.planReviewDebt)}`, `- Operator guidance: waiting=${formatBoolean(snapshot.waitingForOperatorGuidance)}, pending=${formatBoolean(snapshot.pendingOperatorGuidance)}`);
|
|
249
251
|
if (snapshot.lastMeaningfulEvent) {
|
|
250
252
|
lines.push(`- Last progress: ${snapshot.lastMeaningfulEvent.type} - ${snapshot.lastMeaningfulEvent.summary}`);
|
|
251
253
|
}
|
|
@@ -337,6 +339,14 @@ function shouldShowScope(snapshot) {
|
|
|
337
339
|
function formatBoolean(value) {
|
|
338
340
|
return value ? 'yes' : 'no';
|
|
339
341
|
}
|
|
342
|
+
function formatPlanReviewDebtLine(debt) {
|
|
343
|
+
if (debt.total === 0) {
|
|
344
|
+
return '0';
|
|
345
|
+
}
|
|
346
|
+
const rounds = [...new Set(debt.items.map((item) => item.originRound).filter((round) => round !== null))].sort((a, b) => a - b);
|
|
347
|
+
const roundsSuffix = rounds.length > 0 ? ` (rounds ${rounds.join(', ')})` : '';
|
|
348
|
+
return `${debt.total}${roundsSuffix}`;
|
|
349
|
+
}
|
|
340
350
|
function formatLockSummary(lock) {
|
|
341
351
|
switch (lock.kind) {
|
|
342
352
|
case 'none':
|
|
@@ -513,6 +523,10 @@ function summarizeBlocker(state, events, invalidDerivedPlanPayloadPath, guidance
|
|
|
513
523
|
};
|
|
514
524
|
}
|
|
515
525
|
const candidates = [
|
|
526
|
+
{
|
|
527
|
+
reason: nonEmptyString(state.blockerReason),
|
|
528
|
+
source: 'RUN_STATE.json blocker reason',
|
|
529
|
+
},
|
|
516
530
|
{
|
|
517
531
|
reason: guidance?.category === 'scope_accounting_guardrail' ? guidance.summary : null,
|
|
518
532
|
source: 'blocked guidance',
|
|
@@ -903,6 +917,19 @@ function summarizeFindings(state) {
|
|
|
903
917
|
deferred: state.findings.filter((finding) => finding.status === 'deferred').length,
|
|
904
918
|
};
|
|
905
919
|
}
|
|
920
|
+
function summarizePlanReviewDebt(state) {
|
|
921
|
+
const toItem = (item, inherited) => ({
|
|
922
|
+
canonicalId: item.canonicalId,
|
|
923
|
+
findingClass: item.findingClass ?? null,
|
|
924
|
+
originRound: item.originRound ?? null,
|
|
925
|
+
inherited,
|
|
926
|
+
});
|
|
927
|
+
const items = [
|
|
928
|
+
...state.inheritedPlanReviewDebt.map((item) => toItem(item, true)),
|
|
929
|
+
...state.planReviewDebt.map((item) => toItem(item, false)),
|
|
930
|
+
];
|
|
931
|
+
return { total: items.length, items };
|
|
932
|
+
}
|
|
906
933
|
function summarizeCommits(state) {
|
|
907
934
|
return {
|
|
908
935
|
initialBaseCommit: state.initialBaseCommit,
|
package/dist/neal/support.js
CHANGED
|
@@ -8,9 +8,9 @@ function appendInteractiveBlockedRecoverySection(lines, title, recovery, options
|
|
|
8
8
|
if (recovery.pendingDirective) {
|
|
9
9
|
lines.push(`- Pending terminal directive at ${recovery.pendingDirective.recordedAt}: ${recovery.pendingDirective.operatorGuidance}`);
|
|
10
10
|
}
|
|
11
|
-
if (recovery.
|
|
12
|
-
const advice = recovery.
|
|
13
|
-
lines.push(`-
|
|
11
|
+
if (recovery.consultantAdvice) {
|
|
12
|
+
const advice = recovery.consultantAdvice;
|
|
13
|
+
lines.push(`- Consultant advice at ${advice.recordedAt} (read-only, not auto-applied):`, ` - Recoverable: ${advice.recoverable ? 'yes' : 'no'}`, ` - Triage category: ${advice.triageCategory}`, ` - Suggested directive: ${advice.resolutionDirective || 'n/a'}`, ` - Rationale: ${advice.rationale}`);
|
|
14
14
|
}
|
|
15
15
|
if (recovery.turns.length === 0) {
|
|
16
16
|
lines.push('- Operator guidance: pending');
|
package/docs/README.md
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Docs
|
|
2
2
|
|
|
3
|
-
These
|
|
4
|
-
|
|
3
|
+
These are the current product and implementation references. Historical plans
|
|
4
|
+
don't belong here.
|
|
5
5
|
|
|
6
|
-
- [architecture.md](architecture.md):
|
|
7
|
-
- [providers.md](providers.md):
|
|
8
|
-
- [
|
|
9
|
-
- [compat.md](compat.md):
|
|
10
|
-
- [
|
|
11
|
-
- [
|
|
12
|
-
- [
|
|
13
|
-
- [
|
|
14
|
-
- [demo.md](demo.md): safe `asciinema` terminal demo recording workflow
|
|
6
|
+
- [architecture.md](architecture.md): how neal is built (**start here**)
|
|
7
|
+
- [providers.md](providers.md): provider capabilities, permissions, and adapter contracts
|
|
8
|
+
- [compatible-models.md](compatible-models.md): dated `neal compat` results
|
|
9
|
+
- [compat.md](compat.md): the model-compatibility smoke test and PASS/FAIL contract
|
|
10
|
+
- [plan-format.md](plan-format.md): executable plan shapes and selected-plan Git behavior
|
|
11
|
+
- [troubleshooting.md](troubleshooting.md): common setup, run, lock, and squash failures
|
|
12
|
+
- [automation.md](automation.md): exit codes, status JSON, and harness behavior
|
|
13
|
+
- [storage.md](storage.md): project-local `.neal/` storage and artifact classes
|
|
15
14
|
- [state-machine.md](state-machine.md): persisted run and queue state invariants
|
|
16
|
-
- [
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
19
|
-
|
|
15
|
+
- [review-convergence.md](review-convergence.md): plan-review finding classes, debt, and convergence
|
|
16
|
+
- [prompt-specs.md](prompt-specs.md): prompt-spec inventory and ownership boundaries
|
|
17
|
+
- [prompt-evals.md](prompt-evals.md): prompt versioning and reviewer-recall evals
|
|
18
|
+
- [adjudicator-inventory.md](adjudicator-inventory.md): shared coder/reviewer loop inventory
|
|
19
|
+
- [maintenance.md](maintenance.md): dependency updates and versioning
|
|
20
|
+
- [release.md](release.md): manual release process and SDK-update policy
|
|
21
|
+
- [demo.md](demo.md): safe `asciinema` recording workflow
|
|
22
|
+
- [../examples/issue-triage-js/README.md](../examples/issue-triage-js/README.md):
|
|
23
|
+
dependency-free local example and optional live neal run
|
|
20
24
|
|
|
21
|
-
The public README
|
|
22
|
-
contracts that help maintain or extend
|
|
25
|
+
The public README owns the user-facing workflow. Keep these docs focused on
|
|
26
|
+
contracts that help maintain or extend neal.
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Adjudicator inventory
|
|
2
2
|
|
|
3
3
|
## Scope
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The current contract for neal's adjudicator layer covers the shared
|
|
6
6
|
coder/reviewer loop vocabulary, the typed adjudication specs, the read-only
|
|
7
|
-
loops that reuse the same contract, and the
|
|
7
|
+
loops that reuse the same contract, and the consultant.
|
|
8
8
|
|
|
9
9
|
Two things are explicit:
|
|
10
10
|
|
|
11
|
-
-
|
|
11
|
+
- neal uses the terms `adjudicator` and `adjudication spec`.
|
|
12
12
|
- Only recurring coder/reviewer loop mechanics belong to the shared
|
|
13
13
|
adjudicator. Prompt ownership stays under `src/neal/prompts/` (see
|
|
14
|
-
[
|
|
14
|
+
[prompt-specs.md](prompt-specs.md)) and transition semantics stay in the
|
|
15
15
|
orchestrator layers.
|
|
16
16
|
|
|
17
17
|
The typed contract for writer-run adjudication specs lives in
|
|
@@ -22,7 +22,7 @@ are attached to each in-scope writer-run adjudication spec as `loopContract`
|
|
|
22
22
|
metadata. Read-only `neal review` findings use the same contract-only pattern
|
|
23
23
|
with artifacts under `.neal/reviews/<review-id>/`.
|
|
24
24
|
|
|
25
|
-
## Module
|
|
25
|
+
## Module map
|
|
26
26
|
|
|
27
27
|
| Module | Owns |
|
|
28
28
|
| --- | --- |
|
|
@@ -31,10 +31,10 @@ with artifacts under `.neal/reviews/<review-id>/`.
|
|
|
31
31
|
| `src/neal/adjudicator/planning.ts` | Planning-family context resolution plus reviewer and plan-response round adjudication for `plan_review` and `derived_plan_review` |
|
|
32
32
|
| `src/neal/adjudicator/execute.ts` | Execute-family context resolution, reviewer and coder-response round adjudication, findings synthesis, convergence detection (reopened canonical, stuck window), disposition resolution |
|
|
33
33
|
| `src/neal/adjudicator/final-completion.ts` | Final-completion context resolution plus summary and reviewer round adjudication |
|
|
34
|
-
| `src/neal/adjudicator/
|
|
34
|
+
| `src/neal/adjudicator/consultant.ts` | Read-only triage of eligible blocked states (see Consultant below) |
|
|
35
35
|
| `src/neal/adjudicator/artifacts.ts` | Resolving the active adjudication contract from run state and rendering it into artifacts |
|
|
36
36
|
|
|
37
|
-
## Ownership
|
|
37
|
+
## Ownership split
|
|
38
38
|
|
|
39
39
|
- Shared loop contract (`contracts.ts`):
|
|
40
40
|
- loop kind, side-effect policy, allowed outcomes, terminal outcomes,
|
|
@@ -42,7 +42,7 @@ with artifacts under `.neal/reviews/<review-id>/`.
|
|
|
42
42
|
- import-time validation that every in-scope adjudication spec declares the
|
|
43
43
|
shared contract fields, plus validation for the contract-only
|
|
44
44
|
review-findings loop
|
|
45
|
-
- descriptive metadata only
|
|
45
|
+
- descriptive metadata only. Runtime phase routing and state mutation stay in
|
|
46
46
|
the transition layer
|
|
47
47
|
- Adjudication spec (`specs.ts`):
|
|
48
48
|
- coder and reviewer prompt-surface references, validated at import time
|
|
@@ -53,8 +53,8 @@ with artifacts under `.neal/reviews/<review-id>/`.
|
|
|
53
53
|
- validated allowed transition outcomes declared per spec
|
|
54
54
|
- Adjudicator helpers (`planning.ts`, `execute.ts`, `final-completion.ts`):
|
|
55
55
|
- per-family context resolution from run state
|
|
56
|
-
- reviewer/coder round invocation, including
|
|
57
|
-
reviewers
|
|
56
|
+
- reviewer/coder round invocation, including supplying the commit-range diff
|
|
57
|
+
to read-only reviewers that lack their own range-diff tool
|
|
58
58
|
- execute-family findings synthesis, reopened-finding and stuck-window
|
|
59
59
|
detection, and disposition resolution
|
|
60
60
|
- Transition layer (`src/neal/orchestrator.ts`, `src/neal/orchestrator/run-loop.ts`,
|
|
@@ -71,7 +71,7 @@ with artifacts under `.neal/reviews/<review-id>/`.
|
|
|
71
71
|
- `createdCommits` and final-commit consequences
|
|
72
72
|
- Prompt-spec layer:
|
|
73
73
|
- prompt identity, builders, and role/task ownership under
|
|
74
|
-
`src/neal/prompts/` (see [
|
|
74
|
+
`src/neal/prompts/` (see [prompt-specs.md](prompt-specs.md))
|
|
75
75
|
|
|
76
76
|
`transitionSignals` in `specs.ts` are validated allowed outcomes, not a
|
|
77
77
|
dispatch table. Import-time validation checks each spec against one explicit
|
|
@@ -81,7 +81,7 @@ re-checks the resolved outcome against the active adjudication spec, and the
|
|
|
81
81
|
transition layer still maps those outcomes explicitly in runtime code rather
|
|
82
82
|
than dispatching off `transitionSignals` directly.
|
|
83
83
|
|
|
84
|
-
## Transition
|
|
84
|
+
## Transition signals
|
|
85
85
|
|
|
86
86
|
| Spec | Family | Validated transition signals |
|
|
87
87
|
| --- | --- | --- |
|
|
@@ -93,7 +93,7 @@ than dispatching off `transitionSignals` directly.
|
|
|
93
93
|
The `AdjudicationTransitionSignal` union also declares `leave_adjacent`, which
|
|
94
94
|
no in-scope spec or family currently uses.
|
|
95
95
|
|
|
96
|
-
## Loop
|
|
96
|
+
## Loop contracts
|
|
97
97
|
|
|
98
98
|
Every in-scope spec declares allowed outcomes
|
|
99
99
|
`accepted, revise, blocked, failed, cap_reached` with terminal outcomes
|
|
@@ -106,12 +106,12 @@ Every in-scope spec declares allowed outcomes
|
|
|
106
106
|
| `execute_review` | `execute` | `code_changes` | `state.maxRounds` (review iterations) | `implementation_scope` at `state.createdCommits` plus `state.reviewMarkdownPath` |
|
|
107
107
|
| `final_completion_review` | `final_completion` | `code_changes` | `state.finalCompletionContinueExecutionMax` (continued execution) | `final_completion_review` at `FINAL_COMPLETION_REVIEW.md` and `state.finalCompletionReviewVerdict` |
|
|
108
108
|
|
|
109
|
-
## In-
|
|
109
|
+
## In-scope adjudication specs
|
|
110
110
|
|
|
111
111
|
### `plan_review`
|
|
112
112
|
|
|
113
113
|
- Current loop surfaces: `runCoderPlanRound`, `runPlanReviewerRound`, `runCoderPlanResponseRound(reviewMode=plan)`
|
|
114
|
-
- Artifact under review: top-level
|
|
114
|
+
- Artifact under review: top-level neal-executable plan markdown
|
|
115
115
|
- Prompt surfaces:
|
|
116
116
|
- coder primary: `plan_author.primary` via `buildPlanningPrompt`
|
|
117
117
|
- coder response: `plan_author.response` via `buildCoderPlanResponsePrompt`
|
|
@@ -120,11 +120,11 @@ Every in-scope spec declares allowed outcomes
|
|
|
120
120
|
- coder primary: `buildCoderPlanSchema` / `validateCoderPlanPayload`
|
|
121
121
|
- coder response: `buildCoderPlanResponseSchema` / `validateCoderPlanResponsePayload`
|
|
122
122
|
- reviewer: `buildPlanReviewerSchema` / `PlanReviewerPayload`
|
|
123
|
-
- provider surfaces: coder primary/response use `coder_structured_schema
|
|
123
|
+
- provider surfaces: coder primary/response use `coder_structured_schema` and reviewer uses `structured_advisor_schema`
|
|
124
124
|
- Convergence rule:
|
|
125
125
|
- settle when reviewer returns no open findings and the plan remains structurally valid
|
|
126
126
|
- revise when findings route back to required or optional plan response
|
|
127
|
-
- block when coder returns structured `action=blocked
|
|
127
|
+
- block when coder returns structured `action=blocked`. Round code still renders compatibility markers for downstream state
|
|
128
128
|
- Transition targets: `accept_plan`, `request_revision`, `optional_revision`, `block_for_operator`
|
|
129
129
|
|
|
130
130
|
### `derived_plan_review`
|
|
@@ -137,7 +137,7 @@ Every in-scope spec declares allowed outcomes
|
|
|
137
137
|
- Output contracts:
|
|
138
138
|
- coder response: `buildCoderPlanResponseSchema` / `validateCoderPlanResponsePayload`
|
|
139
139
|
- reviewer: `buildPlanReviewerSchema` / `PlanReviewerPayload`
|
|
140
|
-
- provider surfaces: coder response uses `coder_structured_schema
|
|
140
|
+
- provider surfaces: coder response uses `coder_structured_schema` and reviewer uses `structured_advisor_schema`
|
|
141
141
|
- Convergence rule:
|
|
142
142
|
- settle when reviewer returns no open findings and the derived plan is safe to adopt
|
|
143
143
|
- revise through the same planning-family response loop as ordinary plan review
|
|
@@ -157,7 +157,7 @@ Every in-scope spec declares allowed outcomes
|
|
|
157
157
|
- coder primary: `buildCoderScopeSchema` / `validateCoderScopePayload`
|
|
158
158
|
- coder response: `buildCoderResponseSchema` / `validateCoderResponsePayload`
|
|
159
159
|
- reviewer: `buildReviewerSchema` / `ReviewerPayload`
|
|
160
|
-
- provider surfaces: coder primary/response use `coder_structured_schema
|
|
160
|
+
- provider surfaces: coder primary/response use `coder_structured_schema` and reviewer uses `structured_advisor_schema`
|
|
161
161
|
- Convergence rule:
|
|
162
162
|
- settle when the reviewer returns no blocking findings and
|
|
163
163
|
`meaningfulProgressAction === accept`, including top-level scopes already
|
|
@@ -165,13 +165,13 @@ Every in-scope spec declares allowed outcomes
|
|
|
165
165
|
`advance_parent` is downgraded to `accept` with an explanatory rationale)
|
|
166
166
|
- `advance_parent` is a distinct empty-derived-scope parent-advancement
|
|
167
167
|
signal: it settles by finalizing the parent objective only when
|
|
168
|
-
deterministic classification finds it eligible
|
|
168
|
+
deterministic classification finds it eligible. An eligible empty-derived
|
|
169
169
|
classification can also upgrade a findings-free `block_for_operator` to
|
|
170
170
|
`advance_parent`
|
|
171
171
|
- revise when findings reopen `coder_response` or `coder_optional_response`
|
|
172
172
|
- block when reviewer returns `block_for_operator`, `replace_plan`, or an
|
|
173
173
|
unsafe `advance_parent`, or coder returns structured `action=blocked` /
|
|
174
|
-
`action=split_plan
|
|
174
|
+
`action=split_plan`. Round code still renders compatibility markers for
|
|
175
175
|
downstream state
|
|
176
176
|
- Transition targets: `accept_scope`, `request_revision`, `optional_revision`, `block_for_operator`, `replace_plan`, `advance_parent`
|
|
177
177
|
- Public review note:
|
|
@@ -182,7 +182,7 @@ Every in-scope spec declares allowed outcomes
|
|
|
182
182
|
### `final_completion_review`
|
|
183
183
|
|
|
184
184
|
- Current loop surfaces: `runCoderFinalCompletionSummaryRound`, `runReviewerFinalCompletionRound`
|
|
185
|
-
- Artifact under review: whole-plan completion packet assembled after the terminal execute scope settles, including aggregate review context for `initialBaseCommit..finalCommit` when
|
|
185
|
+
- Artifact under review: whole-plan completion packet assembled after the terminal execute scope settles, including aggregate review context for `initialBaseCommit..finalCommit` when neal can read that range
|
|
186
186
|
- Prompt surfaces:
|
|
187
187
|
- coder: `completion_coder.final_completion` via `buildFinalCompletionSummaryPrompt`
|
|
188
188
|
- reviewer: `completion_reviewer.final_completion` via `buildFinalCompletionReviewerPrompt`
|
|
@@ -192,7 +192,7 @@ Every in-scope spec declares allowed outcomes
|
|
|
192
192
|
- provider surfaces: coder and reviewer both use `structured_advisor_schema`
|
|
193
193
|
- Convergence rule:
|
|
194
194
|
- settle when reviewer returns one of the three whole-plan decisions
|
|
195
|
-
- no coder-response revision round exists
|
|
195
|
+
- no coder-response revision round exists. `continue_execution` hands control
|
|
196
196
|
back to execute transitions rather than mutating the adjudicator loop
|
|
197
197
|
- Transition targets: `accept_complete`, `continue_execution`, `block_for_operator`
|
|
198
198
|
- Aggregate review context:
|
|
@@ -200,7 +200,7 @@ Every in-scope spec declares allowed outcomes
|
|
|
200
200
|
`initialBaseCommit` and resolved final commit, then records commit
|
|
201
201
|
subjects, diff stat, and changed files when available
|
|
202
202
|
- if the aggregate range cannot be read, the packet records an explicit
|
|
203
|
-
unavailable reason
|
|
203
|
+
unavailable reason. The reviewer prompt treats that as a completion-review
|
|
204
204
|
evidence gap rather than proof of correctness
|
|
205
205
|
- the final completion reviewer shares the adversarial falsification,
|
|
206
206
|
verification-skepticism, regression-preservation, and
|
|
@@ -208,7 +208,7 @@ Every in-scope spec declares allowed outcomes
|
|
|
208
208
|
`src/neal/prompts/review-doctrine.ts`, but keeps the final-completion
|
|
209
209
|
verdict schema instead of the ordinary scope-review findings schema
|
|
210
210
|
|
|
211
|
-
## Contract-
|
|
211
|
+
## Contract-only read-only loops
|
|
212
212
|
|
|
213
213
|
### `review`
|
|
214
214
|
|
|
@@ -224,7 +224,7 @@ Every in-scope spec declares allowed outcomes
|
|
|
224
224
|
- settle when reviewer returns `accepted` with final markdown
|
|
225
225
|
- revise when reviewer returns concrete findings
|
|
226
226
|
- block when reviewer returns `blocked`, provider validation fails, or
|
|
227
|
-
protected writer state changes
|
|
227
|
+
protected writer state changes. `cap_reached` when the configured
|
|
228
228
|
review-round cap is reached without acceptance
|
|
229
229
|
- Loop contract (`REVIEW_FINDINGS_ADJUDICATED_LOOP_CONTRACT` in `contracts.ts`):
|
|
230
230
|
- loop kind: `review`
|
|
@@ -233,54 +233,55 @@ Every in-scope spec declares allowed outcomes
|
|
|
233
233
|
- terminal artifact: `.neal/reviews/<review-id>/REVIEW_FINAL.md`
|
|
234
234
|
- State ownership:
|
|
235
235
|
- review artifacts live under `.neal/reviews/<review-id>/`
|
|
236
|
-
- review never becomes a writer-run command path
|
|
236
|
+
- review never becomes a writer-run command path. A read-only guard asserts
|
|
237
237
|
that `.neal/current.json`, `.neal/current-queue.json`, and run-local
|
|
238
238
|
`RUN_STATE.json` files are unchanged after the loop
|
|
239
239
|
|
|
240
|
-
##
|
|
240
|
+
## Consultant
|
|
241
241
|
|
|
242
|
-
`src/neal/adjudicator/
|
|
242
|
+
`src/neal/adjudicator/consultant.ts` (`runConsultant`) is a
|
|
243
243
|
read-only triage step for blocked writer runs. It decides whether a block is an
|
|
244
|
-
autonomously recoverable misunderstanding
|
|
245
|
-
scope with no new authorization, external state, or scope expansion
|
|
246
|
-
genuine
|
|
244
|
+
autonomously recoverable misunderstanding (resolvable within the existing
|
|
245
|
+
scope with no new authorization, external state, or scope expansion) or a
|
|
246
|
+
genuine blocker (`authorization`, `external_precondition`, `impossible_task`)
|
|
247
247
|
that must escalate to a human. It is not an adjudication spec: it makes no
|
|
248
|
-
commits and no file edits, and only returns a `
|
|
248
|
+
commits and no file edits, and only returns a `ConsultantVerdict`.
|
|
249
249
|
|
|
250
250
|
Gates, enforced at the recovery chokepoint
|
|
251
|
-
(`src/neal/orchestrator/phases/recovery.ts`)
|
|
252
|
-
generic recovery with no
|
|
251
|
+
(`src/neal/orchestrator/phases/recovery.ts`). Any failed gate falls through to
|
|
252
|
+
generic recovery with no consultant invocation:
|
|
253
253
|
|
|
254
|
-
- Source-phase eligibility: `
|
|
254
|
+
- Source-phase eligibility: `CONSULTANT_ELIGIBLE_SOURCE_PHASES` is
|
|
255
255
|
`reviewer_scope`, `reviewer_plan`, `coder_scope`, `coder_response`,
|
|
256
256
|
`coder_optional_response`. Reviewer phases are eligible only for structural
|
|
257
|
-
`review_stuck:` reasons
|
|
258
|
-
- Per-scope budget: `state.
|
|
259
|
-
`neal.
|
|
257
|
+
`review_stuck:` reasons. Any coder block on the coder phases is eligible.
|
|
258
|
+
- Per-scope budget: `state.consultantAttemptCount`, bounded by
|
|
259
|
+
`neal.consultant_max_attempts` (default `1`, `0` disables) and
|
|
260
260
|
reset to `0` at scope boundaries. One invocation consumes one unit whether it
|
|
261
|
-
auto-
|
|
262
|
-
|
|
261
|
+
auto-applies a recoverable verdict (both run modes) or, on a non-recoverable
|
|
262
|
+
verdict, finalizes terminally (unattended) or surfaces the verdict as operator
|
|
263
|
+
advice and yields (attended).
|
|
263
264
|
- Anti-thrash window: a block that repeats a `state.recentBlocks` record with
|
|
264
265
|
the same scope identity (`scopeNumber` + `derivedScopeIndex`), the same
|
|
265
|
-
`sourcePhase`,
|
|
266
|
+
`sourcePhase`, the same normalized blocker key, and no new evidence
|
|
266
267
|
short-circuits to a non-recoverable `impossible_task` verdict without
|
|
267
268
|
running an LLM round. The chokepoint is the sole writer of
|
|
268
|
-
`state.recentBlocks
|
|
269
|
+
`state.recentBlocks`. The consultant module's window helpers are pure.
|
|
269
270
|
|
|
270
|
-
Verdict schema constraints (`
|
|
271
|
-
`
|
|
271
|
+
Verdict schema constraints (`buildConsultantSchema` /
|
|
272
|
+
`validateConsultantVerdictPayload` in `src/neal/agents/schemas.ts`):
|
|
272
273
|
`recoverable=true` is valid only with `triageCategory=misunderstanding` plus a
|
|
273
|
-
non-empty `resolutionDirective
|
|
274
|
-
`misunderstanding
|
|
274
|
+
non-empty `resolutionDirective`. `recoverable=false` must not pair with
|
|
275
|
+
`misunderstanding`. `rationale` must be non-empty. `targetCanonicalIds` is
|
|
275
276
|
optional and defaults to `[]`. A malformed verdict is rejected so it can never
|
|
276
277
|
drive an autonomous recovery.
|
|
277
278
|
|
|
278
|
-
Prompt and round: `
|
|
279
|
+
Prompt and round: `buildConsultantPrompt` in
|
|
279
280
|
`src/neal/agents/prompts.ts` (a single no-read-safe variant that judges
|
|
280
|
-
entirely from
|
|
281
|
+
entirely from neal-inlined context) run through `runConsultantRound`
|
|
281
282
|
in `src/neal/agents/rounds.ts`.
|
|
282
283
|
|
|
283
|
-
## Adjacent
|
|
284
|
+
## Adjacent or non-adjudicator flows
|
|
284
285
|
|
|
285
286
|
- `interactive_blocked_recovery` (declared in `ADJUDICATION_ADJACENT_FLOWS`):
|
|
286
287
|
- current entrypoint: `runInteractiveBlockedRecoveryPhase`
|
|
@@ -288,23 +289,23 @@ in `src/neal/agents/rounds.ts`.
|
|
|
288
289
|
than forming a pure coder/reviewer adjudication loop, so this remains
|
|
289
290
|
transition-layer-owned
|
|
290
291
|
|
|
291
|
-
## Spec
|
|
292
|
+
## Spec family mapping
|
|
292
293
|
|
|
293
294
|
- Planning family: `plan_review`, `derived_plan_review`
|
|
294
295
|
- Execute family: `execute_review` (meaningful-progress remains a capability of
|
|
295
296
|
this family, not a separate adjudication spec)
|
|
296
297
|
- Final-completion family: `final_completion_review`
|
|
297
298
|
- Contract-only read-only loops: `review`
|
|
298
|
-
- Adjacent read-only triage: the
|
|
299
|
+
- Adjacent read-only triage: the consultant (not a spec)
|
|
299
300
|
|
|
300
301
|
Shared reviewer doctrine does not collapse these families. It is prompt wording
|
|
301
|
-
reused across execute-scope review, plan review, and final completion review
|
|
302
|
-
|
|
302
|
+
reused across execute-scope review, plan review, and final completion review.
|
|
303
|
+
Schemas, allowed transition outcomes, and artifact/context packets remain
|
|
303
304
|
family-specific.
|
|
304
305
|
|
|
305
306
|
New loop variants should extend the adjudication-spec family or remain
|
|
306
|
-
explicitly adjacent
|
|
307
|
+
explicitly adjacent. They should not reintroduce custom coder/reviewer loop
|
|
307
308
|
wiring inside `src/neal/orchestrator.ts`. When adding or changing a spec, keep
|
|
308
309
|
`transitionSignals`, the family runtime contract, and the loop contract in
|
|
309
|
-
sync
|
|
310
|
+
sync. `specs.ts` and `contracts.ts` validate all three at import time, so
|
|
310
311
|
drift fails fast in tests and at startup.
|