@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.
Files changed (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +527 -0
  3. package/SECURITY.md +91 -0
  4. package/config.yml +104 -0
  5. package/dist/neal/activity-footer.js +177 -0
  6. package/dist/neal/activity-reporting.js +1 -0
  7. package/dist/neal/adjudicator/artifacts.js +58 -0
  8. package/dist/neal/adjudicator/blocked-adjudicator.js +223 -0
  9. package/dist/neal/adjudicator/contracts.js +139 -0
  10. package/dist/neal/adjudicator/execute.js +611 -0
  11. package/dist/neal/adjudicator/final-completion.js +104 -0
  12. package/dist/neal/adjudicator/planning.js +145 -0
  13. package/dist/neal/adjudicator/specs.js +453 -0
  14. package/dist/neal/agents/prompts.js +120 -0
  15. package/dist/neal/agents/rounds.js +706 -0
  16. package/dist/neal/agents/schemas.js +832 -0
  17. package/dist/neal/agents/structured-coder.js +82 -0
  18. package/dist/neal/agents/structured-json.js +528 -0
  19. package/dist/neal/agents.js +4 -0
  20. package/dist/neal/atomic-write.js +18 -0
  21. package/dist/neal/blocked-guidance.js +406 -0
  22. package/dist/neal/cli.js +471 -0
  23. package/dist/neal/commands/check.js +401 -0
  24. package/dist/neal/commands/compat.js +807 -0
  25. package/dist/neal/commands/interactive-activity.js +57 -0
  26. package/dist/neal/commands/new-run.js +79 -0
  27. package/dist/neal/commands/plan-and-execute.js +44 -0
  28. package/dist/neal/commands/recovery-guidance.js +217 -0
  29. package/dist/neal/commands/resume-run.js +395 -0
  30. package/dist/neal/commands/review.js +21 -0
  31. package/dist/neal/commands/runtime.js +557 -0
  32. package/dist/neal/commands/setup.js +596 -0
  33. package/dist/neal/commands/squash.js +113 -0
  34. package/dist/neal/commands/status.js +33 -0
  35. package/dist/neal/commands/writer-exit-codes.js +42 -0
  36. package/dist/neal/commit-message.js +17 -0
  37. package/dist/neal/config.js +432 -0
  38. package/dist/neal/context/artifacts.js +140 -0
  39. package/dist/neal/context/context.js +324 -0
  40. package/dist/neal/context/inline-review-context.js +131 -0
  41. package/dist/neal/context/reviewer-context.js +166 -0
  42. package/dist/neal/context/shared.js +117 -0
  43. package/dist/neal/context/types.js +1 -0
  44. package/dist/neal/diagnostic.js +208 -0
  45. package/dist/neal/execute-finalization.js +5 -0
  46. package/dist/neal/final-completion-review.js +188 -0
  47. package/dist/neal/final-completion.js +229 -0
  48. package/dist/neal/git.js +339 -0
  49. package/dist/neal/index.js +135 -0
  50. package/dist/neal/interactive-controls.js +85 -0
  51. package/dist/neal/logger.js +102 -0
  52. package/dist/neal/manual-gates.js +121 -0
  53. package/dist/neal/orchestrator/artifacts.js +70 -0
  54. package/dist/neal/orchestrator/completion.js +531 -0
  55. package/dist/neal/orchestrator/failures.js +31 -0
  56. package/dist/neal/orchestrator/notifications.js +175 -0
  57. package/dist/neal/orchestrator/phases/coder.js +516 -0
  58. package/dist/neal/orchestrator/phases/planning.js +540 -0
  59. package/dist/neal/orchestrator/phases/recovery.js +798 -0
  60. package/dist/neal/orchestrator/phases/review.js +136 -0
  61. package/dist/neal/orchestrator/phases/shared.js +279 -0
  62. package/dist/neal/orchestrator/run-loop.js +113 -0
  63. package/dist/neal/orchestrator/split-plan.js +235 -0
  64. package/dist/neal/orchestrator/transitions.js +309 -0
  65. package/dist/neal/orchestrator.js +215 -0
  66. package/dist/neal/phase-display.js +27 -0
  67. package/dist/neal/plan-doc.js +154 -0
  68. package/dist/neal/plan-queue.js +1092 -0
  69. package/dist/neal/plan-refinement.js +39 -0
  70. package/dist/neal/plan-validation.js +525 -0
  71. package/dist/neal/progress.js +237 -0
  72. package/dist/neal/prompts/assert-builder.js +13 -0
  73. package/dist/neal/prompts/execute.js +290 -0
  74. package/dist/neal/prompts/guidance.js +70 -0
  75. package/dist/neal/prompts/planning.js +313 -0
  76. package/dist/neal/prompts/review-doctrine.js +142 -0
  77. package/dist/neal/prompts/shared.js +101 -0
  78. package/dist/neal/prompts/specialized.js +212 -0
  79. package/dist/neal/prompts/specs.js +572 -0
  80. package/dist/neal/providers/anthropic-claude.js +1599 -0
  81. package/dist/neal/providers/detection.js +139 -0
  82. package/dist/neal/providers/generic-agentic-tools.js +586 -0
  83. package/dist/neal/providers/generic-agentic.js +1238 -0
  84. package/dist/neal/providers/liveness.js +151 -0
  85. package/dist/neal/providers/openai-codex.js +1014 -0
  86. package/dist/neal/providers/openai-compatible.js +654 -0
  87. package/dist/neal/providers/registry.js +389 -0
  88. package/dist/neal/providers/telemetry.js +208 -0
  89. package/dist/neal/providers/types.js +21 -0
  90. package/dist/neal/recovery-artifacts.js +50 -0
  91. package/dist/neal/resume-decision.js +220 -0
  92. package/dist/neal/resume-planner.js +265 -0
  93. package/dist/neal/retrospective.js +391 -0
  94. package/dist/neal/review-debt.js +18 -0
  95. package/dist/neal/review-findings/artifacts.js +173 -0
  96. package/dist/neal/review-findings/prompts.js +172 -0
  97. package/dist/neal/review-findings/provider.js +330 -0
  98. package/dist/neal/review-findings/run.js +373 -0
  99. package/dist/neal/review-findings/types.js +1 -0
  100. package/dist/neal/review-mode.js +67 -0
  101. package/dist/neal/review.js +137 -0
  102. package/dist/neal/run-lock.js +334 -0
  103. package/dist/neal/run-metrics.js +355 -0
  104. package/dist/neal/run-narrative-types.js +1 -0
  105. package/dist/neal/run-narrative.js +1374 -0
  106. package/dist/neal/run-registry.js +218 -0
  107. package/dist/neal/run-status.js +25 -0
  108. package/dist/neal/scopes.js +451 -0
  109. package/dist/neal/sensitive-text.js +8 -0
  110. package/dist/neal/squash-message.js +379 -0
  111. package/dist/neal/squash.js +591 -0
  112. package/dist/neal/state-invariants.js +496 -0
  113. package/dist/neal/state-views.js +344 -0
  114. package/dist/neal/state.js +887 -0
  115. package/dist/neal/status-footer.js +258 -0
  116. package/dist/neal/status.js +1260 -0
  117. package/dist/neal/storage-paths.js +57 -0
  118. package/dist/neal/support.js +58 -0
  119. package/dist/neal/terminal-narrator.js +435 -0
  120. package/dist/neal/types.js +1 -0
  121. package/dist/neal/verification-events.js +81 -0
  122. package/dist/neal/version.js +37 -0
  123. package/dist/neal/worktree-status.js +137 -0
  124. package/dist/notifier.js +44 -0
  125. package/docs/ADJUDICATOR_INVENTORY.md +310 -0
  126. package/docs/PROMPT_SPECS.md +266 -0
  127. package/docs/README.md +22 -0
  128. package/docs/architecture.md +113 -0
  129. package/docs/assets/neal-execution-flow.png +0 -0
  130. package/docs/automation.md +65 -0
  131. package/docs/comparison.md +105 -0
  132. package/docs/compat.md +269 -0
  133. package/docs/compatible-models.md +135 -0
  134. package/docs/demo.md +55 -0
  135. package/docs/maintenance.md +64 -0
  136. package/docs/plan-format.md +213 -0
  137. package/docs/providers.md +751 -0
  138. package/docs/release.md +147 -0
  139. package/docs/state-machine.md +266 -0
  140. package/docs/storage.md +207 -0
  141. package/docs/troubleshooting.md +152 -0
  142. package/examples/compat/add-edit-verify/PLAN.md +29 -0
  143. package/examples/compat/add-edit-verify/broken.diff +8 -0
  144. package/examples/compat/add-edit-verify/good.diff +8 -0
  145. package/examples/compat/add-edit-verify/package.json +5 -0
  146. package/examples/compat/add-edit-verify/src/add.js +2 -0
  147. package/examples/compat/add-edit-verify/test/add.test.js +9 -0
  148. package/examples/compat/is-even-add-test/PLAN.md +30 -0
  149. package/examples/compat/is-even-add-test/broken.diff +11 -0
  150. package/examples/compat/is-even-add-test/good.diff +11 -0
  151. package/examples/compat/is-even-add-test/package.json +5 -0
  152. package/examples/compat/is-even-add-test/src/is-even.js +3 -0
  153. package/examples/compat/is-even-add-test/test/is-even.test.js +9 -0
  154. package/examples/compat/manifest.json +60 -0
  155. package/examples/compat/plan-greeting/ISSUE.md +25 -0
  156. package/examples/compat/plan-greeting/package.json +5 -0
  157. package/examples/compat/plan-greeting/src/greet.js +2 -0
  158. package/examples/compat/plan-greeting/test/greet.test.js +8 -0
  159. package/examples/compat/reverse-grep-edit/PLAN.md +32 -0
  160. package/examples/compat/reverse-grep-edit/broken.diff +12 -0
  161. package/examples/compat/reverse-grep-edit/good.diff +12 -0
  162. package/examples/compat/reverse-grep-edit/package.json +5 -0
  163. package/examples/compat/reverse-grep-edit/src/strings.js +10 -0
  164. package/examples/compat/reverse-grep-edit/test/strings.test.js +15 -0
  165. package/examples/issue-triage-js/PLAN.md +83 -0
  166. package/examples/issue-triage-js/README.md +76 -0
  167. package/examples/issue-triage-js/package.json +9 -0
  168. package/examples/issue-triage-js/src/issue-triage.js +87 -0
  169. package/examples/issue-triage-js/test/issue-triage.test.js +107 -0
  170. package/package.json +70 -0
@@ -0,0 +1,137 @@
1
+ import { realpathSync } from 'node:fs';
2
+ import { isAbsolute, normalize, relative, resolve } from 'node:path';
3
+ const LIKELY_SCRATCH_ROOTS = new Set([
4
+ 'build_review',
5
+ 'build-review',
6
+ 'review_build',
7
+ 'review-build',
8
+ 'review_scratch',
9
+ 'review-scratch',
10
+ 'reviewer_scratch',
11
+ 'reviewer-scratch',
12
+ 'scratch',
13
+ ]);
14
+ const WRAPPER_OWNED_PREFIXES = ['.neal/', '.forge/'];
15
+ const WRAPPER_OWNED_PATHS = new Set(['.neal', '.forge', 'CURRENT_PLAN.md']);
16
+ export function toStoredWorktreePath(cwd, path) {
17
+ const absoluteCwd = resolveExistingPath(resolve(cwd));
18
+ const absolutePath = resolveExistingPath(isAbsolute(path) ? resolve(path) : resolve(absoluteCwd, path));
19
+ const relativePath = relative(absoluteCwd, absolutePath);
20
+ if (relativePath && !relativePath.startsWith('..') && !isAbsolute(relativePath)) {
21
+ return normalize(relativePath);
22
+ }
23
+ return absolutePath;
24
+ }
25
+ function resolveExistingPath(path) {
26
+ try {
27
+ return realpathSync.native(path);
28
+ }
29
+ catch {
30
+ return path;
31
+ }
32
+ }
33
+ export function filterAllowedDirtyPathStatus(cwd, statusOutput, allowedDirtyPaths) {
34
+ const allowedPaths = new Set(allowedDirtyPaths.flatMap((path) => allowedDirtyPathKeys(cwd, path)));
35
+ return statusOutput
36
+ .split('\n')
37
+ .map((line) => line.trimEnd())
38
+ .filter(Boolean)
39
+ .filter((line) => {
40
+ const paths = parseWorktreeStatusLine(line)?.paths ?? [];
41
+ return paths.length === 0 || paths.some((path) => !allowedPaths.has(toStoredWorktreePath(cwd, path)));
42
+ })
43
+ .join('\n');
44
+ }
45
+ function allowedDirtyPathKeys(cwd, path) {
46
+ const storedPath = toStoredWorktreePath(cwd, path);
47
+ if (isAbsolute(path)) {
48
+ return [storedPath];
49
+ }
50
+ return [storedPath, normalize(path)];
51
+ }
52
+ export function filterWrapperOwnedWorktreeStatus(statusOutput) {
53
+ return statusOutput
54
+ .split('\n')
55
+ .map((line) => line.trimEnd())
56
+ .filter(Boolean)
57
+ .filter((line) => {
58
+ const paths = parseWorktreeStatusLine(line)?.paths ?? [];
59
+ return paths.length === 0 || paths.some((path) => !isWrapperOwnedPath(path));
60
+ })
61
+ .join('\n');
62
+ }
63
+ export function parseWorktreeStatusLine(line) {
64
+ const raw = line.trimEnd();
65
+ if (!raw) {
66
+ return null;
67
+ }
68
+ const pathText = getStatusLinePathText(raw);
69
+ if (!pathText) {
70
+ return {
71
+ raw,
72
+ pathText,
73
+ paths: [],
74
+ };
75
+ }
76
+ return {
77
+ raw,
78
+ pathText,
79
+ paths: pathText.split(' -> ').map((path) => normalize(path)),
80
+ };
81
+ }
82
+ export function getLikelyScratchLeakPaths(statusOutput) {
83
+ const scratchPaths = statusOutput
84
+ .split('\n')
85
+ .map(parseWorktreeStatusLine)
86
+ .filter((entry) => entry !== null)
87
+ .flatMap((entry) => entry.paths)
88
+ .filter(isLikelyProjectScratchPath);
89
+ return [...new Set(scratchPaths)];
90
+ }
91
+ export function formatDirtyWorktreeDiagnostic(args) {
92
+ const scratchPaths = getLikelyScratchLeakPaths(args.statusOutput);
93
+ if (scratchPaths.length === 0) {
94
+ return '';
95
+ }
96
+ const expectedScratchDirs = [...new Set(args.expectedScratchDirs ?? [])].filter(Boolean);
97
+ const lines = [
98
+ 'Likely Neal reviewer scratch leakage detected:',
99
+ ...scratchPaths.map((path) => `- ${path}`),
100
+ '',
101
+ ];
102
+ if (expectedScratchDirs.length > 0) {
103
+ lines.push('Expected Neal scratch location(s):', ...expectedScratchDirs.map((path) => `- ${path}`), '');
104
+ }
105
+ else {
106
+ lines.push('Reviewer scratch should stay under `.neal/runs/<run-id>/scratch/`.', '');
107
+ }
108
+ lines.push('Neal is still blocking because these project-tree paths are not proven Neal-owned. Inspect and move, remove, commit, or stash them outside Neal if appropriate.');
109
+ return lines.join('\n');
110
+ }
111
+ function isLikelyProjectScratchPath(path) {
112
+ if (isAbsolute(path)) {
113
+ return false;
114
+ }
115
+ const normalized = normalize(path).replace(/\\/g, '/').replace(/^\.\//, '');
116
+ const [root] = normalized.split('/');
117
+ return LIKELY_SCRATCH_ROOTS.has(root);
118
+ }
119
+ export function isWrapperOwnedPath(path) {
120
+ if (isAbsolute(path)) {
121
+ return false;
122
+ }
123
+ const normalized = normalize(path)
124
+ .replace(/\\/g, '/')
125
+ .replace(/^\.\//, '')
126
+ .replace(/\/+$/, '');
127
+ return WRAPPER_OWNED_PATHS.has(normalized) || WRAPPER_OWNED_PREFIXES.some((prefix) => normalized.startsWith(prefix));
128
+ }
129
+ function getStatusLinePathText(line) {
130
+ if (line.length >= 3 && line[2] === ' ') {
131
+ return line.slice(3).trim();
132
+ }
133
+ if (line.length >= 2 && line[1] === ' ') {
134
+ return line.slice(2).trim();
135
+ }
136
+ return line.trim();
137
+ }
@@ -0,0 +1,44 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { getNotifyBin } from './neal/config.js';
3
+ export const NOTIFICATION_CHECK_MESSAGE = '[neal] check notification test';
4
+ async function runCommand(command, args) {
5
+ await new Promise((resolvePromise, rejectPromise) => {
6
+ const child = spawn(command, args, {
7
+ shell: false,
8
+ stdio: 'ignore',
9
+ });
10
+ child.on('error', rejectPromise);
11
+ child.on('exit', (code, signal) => {
12
+ if (signal) {
13
+ rejectPromise(new Error(`${command} terminated by signal ${signal}`));
14
+ return;
15
+ }
16
+ if (code === 0) {
17
+ resolvePromise();
18
+ }
19
+ else {
20
+ rejectPromise(new Error(`${command} exited with status ${code}`));
21
+ }
22
+ });
23
+ });
24
+ }
25
+ export async function notify(kind, message, cwd = process.cwd()) {
26
+ const notifyPath = getNotifyBin(cwd);
27
+ if (!notifyPath) {
28
+ return;
29
+ }
30
+ try {
31
+ await runCommand(notifyPath, [message]);
32
+ }
33
+ catch {
34
+ // Notifications are a local convenience; the primary run result wins.
35
+ }
36
+ }
37
+ export async function verifyNotification(cwd = process.cwd(), message = NOTIFICATION_CHECK_MESSAGE) {
38
+ const notifyPath = getNotifyBin(cwd);
39
+ if (!notifyPath) {
40
+ return null;
41
+ }
42
+ await runCommand(notifyPath, [message]);
43
+ return notifyPath;
44
+ }
@@ -0,0 +1,310 @@
1
+ # Neal Adjudicator Inventory
2
+
3
+ ## Scope
4
+
5
+ This document is the current contract for Neal's adjudicator layer: the shared
6
+ coder/reviewer loop vocabulary, the typed adjudication specs, the read-only
7
+ loops that reuse the same contract, and the blocked adjudicator.
8
+
9
+ Two things are explicit:
10
+
11
+ - Neal uses the terms `adjudicator` and `adjudication spec`.
12
+ - Only recurring coder/reviewer loop mechanics belong to the shared
13
+ adjudicator. Prompt ownership stays under `src/neal/prompts/` (see
14
+ [PROMPT_SPECS.md](PROMPT_SPECS.md)) and transition semantics stay in the
15
+ orchestrator layers.
16
+
17
+ The typed contract for writer-run adjudication specs lives in
18
+ [src/neal/adjudicator/specs.ts](../src/neal/adjudicator/specs.ts).
19
+ The shared loop vocabulary and side-effect contract live in
20
+ [src/neal/adjudicator/contracts.ts](../src/neal/adjudicator/contracts.ts) and
21
+ are attached to each in-scope writer-run adjudication spec as `loopContract`
22
+ metadata. Read-only `neal review` findings use the same contract-only pattern
23
+ with artifacts under `.neal/reviews/<review-id>/`.
24
+
25
+ ## Module Map
26
+
27
+ | Module | Owns |
28
+ | --- | --- |
29
+ | `src/neal/adjudicator/specs.ts` | `AdjudicationSpec` type, `ADJUDICATION_SPECS` registry, transition-signal validation, `getAdjudicationSpec`, `assertAdjudicationTransitionSignal`, `getReviewerCapability`, `ADJUDICATION_ADJACENT_FLOWS` |
30
+ | `src/neal/adjudicator/contracts.ts` | `AdjudicatedLoopContract` vocabulary (loop kind, side-effect policy, allowed/terminal outcomes, round-cap and terminal-artifact metadata), import-time contract validation, `resolveReviewedDraftLoopStep`, `REVIEW_FINDINGS_ADJUDICATED_LOOP_CONTRACT` |
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
+ | `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
+ | `src/neal/adjudicator/final-completion.ts` | Final-completion context resolution plus summary and reviewer round adjudication |
34
+ | `src/neal/adjudicator/blocked-adjudicator.ts` | Read-only triage of eligible blocked states (see Blocked Adjudicator below) |
35
+ | `src/neal/adjudicator/artifacts.ts` | Resolving the active adjudication contract from run state and rendering it into artifacts |
36
+
37
+ ## Ownership Split
38
+
39
+ - Shared loop contract (`contracts.ts`):
40
+ - loop kind, side-effect policy, allowed outcomes, terminal outcomes,
41
+ round-cap semantics, and terminal artifact metadata
42
+ - import-time validation that every in-scope adjudication spec declares the
43
+ shared contract fields, plus validation for the contract-only
44
+ review-findings loop
45
+ - descriptive metadata only; runtime phase routing and state mutation stay in
46
+ the transition layer
47
+ - Adjudication spec (`specs.ts`):
48
+ - coder and reviewer prompt-surface references, validated at import time
49
+ against the prompt-spec registry
50
+ - coder and reviewer schema builder / parser / provider output surfaces
51
+ - artifact and context-assembly contract
52
+ - convergence rule
53
+ - validated allowed transition outcomes declared per spec
54
+ - Adjudicator helpers (`planning.ts`, `execute.ts`, `final-completion.ts`):
55
+ - per-family context resolution from run state
56
+ - reviewer/coder round invocation, including inline-context assembly for
57
+ reviewers without repository read access
58
+ - execute-family findings synthesis, reopened-finding and stuck-window
59
+ detection, and disposition resolution
60
+ - Transition layer (`src/neal/orchestrator.ts`, `src/neal/orchestrator/run-loop.ts`,
61
+ `src/neal/orchestrator/phases/*.ts`, `src/neal/orchestrator/transitions.ts`,
62
+ `src/neal/orchestrator/completion.ts`):
63
+ - runnable-phase registries, phase routing, and state mutation
64
+ - live re-check that the resolved outcome is allowed for the active
65
+ adjudication spec (`assertAdjudicationTransitionSignal`)
66
+ - coder-timeout and fresh-session retry handling
67
+ - notification emission
68
+ - split-plan handling
69
+ - interactive blocked-recovery routing
70
+ - adoption/finalization semantics
71
+ - `createdCommits` and final-commit consequences
72
+ - Prompt-spec layer:
73
+ - prompt identity, builders, and role/task ownership under
74
+ `src/neal/prompts/` (see [PROMPT_SPECS.md](PROMPT_SPECS.md))
75
+
76
+ `transitionSignals` in `specs.ts` are validated allowed outcomes, not a
77
+ dispatch table. Import-time validation checks each spec against one explicit
78
+ family-level runtime contract (`FAMILY_RUNTIME_TRANSITION_SIGNALS`) and a
79
+ per-spec required set (`SPEC_RUNTIME_TRANSITION_SIGNALS`), live routing
80
+ re-checks the resolved outcome against the active adjudication spec, and the
81
+ transition layer still maps those outcomes explicitly in runtime code rather
82
+ than dispatching off `transitionSignals` directly.
83
+
84
+ ## Transition Signals
85
+
86
+ | Spec | Family | Validated transition signals |
87
+ | --- | --- | --- |
88
+ | `plan_review` | `plan_review` | `accept_plan`, `request_revision`, `optional_revision`, `block_for_operator` |
89
+ | `derived_plan_review` | `plan_review` | `accept_derived_plan`, `request_revision`, `optional_revision`, `block_for_operator` |
90
+ | `execute_review` | `execute_review` | `accept_scope`, `request_revision`, `optional_revision`, `block_for_operator`, `replace_plan`, `advance_parent` |
91
+ | `final_completion_review` | `final_completion` | `accept_complete`, `continue_execution`, `block_for_operator` |
92
+
93
+ The `AdjudicationTransitionSignal` union also declares `leave_adjacent`, which
94
+ no in-scope spec or family currently uses.
95
+
96
+ ## Loop Contracts
97
+
98
+ Every in-scope spec declares allowed outcomes
99
+ `accepted, revise, blocked, failed, cap_reached` with terminal outcomes
100
+ `accepted, blocked, failed, cap_reached`.
101
+
102
+ | Spec | Loop kind | Side effects | Round cap source | Terminal artifact |
103
+ | --- | --- | --- | --- | --- |
104
+ | `plan_review` | `plan` | `plan_doc_only` | `state.maxRounds` (review iterations) | `plan_document` at `state.planDoc` |
105
+ | `derived_plan_review` | `plan` | `plan_doc_only` | `derivedPlan.counters.maxDerivedPlanReviewRounds` (review iterations) | `derived_plan_document` at `state.derivedPlanPath` |
106
+ | `execute_review` | `execute` | `code_changes` | `state.maxRounds` (review iterations) | `implementation_scope` at `state.createdCommits` plus `state.reviewMarkdownPath` |
107
+ | `final_completion_review` | `final_completion` | `code_changes` | `state.finalCompletionContinueExecutionMax` (continued execution) | `final_completion_review` at `FINAL_COMPLETION_REVIEW.md` and `state.finalCompletionReviewVerdict` |
108
+
109
+ ## In-Scope Adjudication Specs
110
+
111
+ ### `plan_review`
112
+
113
+ - Current loop surfaces: `runCoderPlanRound`, `runPlanReviewerRound`, `runCoderPlanResponseRound(reviewMode=plan)`
114
+ - Artifact under review: top-level Neal-executable plan markdown
115
+ - Prompt surfaces:
116
+ - coder primary: `plan_author.primary` via `buildPlanningPrompt`
117
+ - coder response: `plan_author.response` via `buildCoderPlanResponsePrompt`
118
+ - reviewer: `plan_reviewer.primary` via `buildPlanReviewerPrompt`
119
+ - Output contracts:
120
+ - coder primary: `buildCoderPlanSchema` / `validateCoderPlanPayload`
121
+ - coder response: `buildCoderPlanResponseSchema` / `validateCoderPlanResponsePayload`
122
+ - reviewer: `buildPlanReviewerSchema` / `PlanReviewerPayload`
123
+ - provider surfaces: coder primary/response use `coder_structured_schema`; reviewer uses `structured_advisor_schema`
124
+ - Convergence rule:
125
+ - settle when reviewer returns no open findings and the plan remains structurally valid
126
+ - revise when findings route back to required or optional plan response
127
+ - block when coder returns structured `action=blocked`; round code still renders compatibility markers for downstream state
128
+ - Transition targets: `accept_plan`, `request_revision`, `optional_revision`, `block_for_operator`
129
+
130
+ ### `derived_plan_review`
131
+
132
+ - Current loop surfaces: `runPlanReviewerRound(mode=derived-plan)`, `runCoderPlanResponseRound(reviewMode=derived-plan)`
133
+ - Artifact under review: derived replacement plan for one stale execute scope
134
+ - Prompt surfaces:
135
+ - coder response family: `plan_author.response` via `buildCoderPlanResponsePrompt` (`reviewMode=derived-plan`)
136
+ - reviewer: `plan_reviewer.derived_plan` via `buildPlanReviewerPrompt`
137
+ - Output contracts:
138
+ - coder response: `buildCoderPlanResponseSchema` / `validateCoderPlanResponsePayload`
139
+ - reviewer: `buildPlanReviewerSchema` / `PlanReviewerPayload`
140
+ - provider surfaces: coder response uses `coder_structured_schema`; reviewer uses `structured_advisor_schema`
141
+ - Convergence rule:
142
+ - settle when reviewer returns no open findings and the derived plan is safe to adopt
143
+ - revise through the same planning-family response loop as ordinary plan review
144
+ - block when coder returns structured `action=blocked` or the derived plan remains invalid
145
+ - Transition targets: `accept_derived_plan`, `request_revision`, `optional_revision`, `block_for_operator`
146
+
147
+ ### `execute_review`
148
+
149
+ - Current loop surfaces: `runCoderScopeRound`, `runReviewerRound`, `runCoderResponseRound`
150
+ - Artifact under review: execute-mode scope diff plus persisted meaningful-progress history for the active parent objective
151
+ - Prompt surfaces:
152
+ - coder primary: `scope_coder.primary` via `buildScopePrompt`
153
+ - coder response: `scope_coder.response` via `buildCoderResponsePrompt`
154
+ - reviewer: `scope_reviewer.primary` via `buildReviewerPrompt`
155
+ - reviewer capability: `scope_reviewer.meaningful_progress` via `buildReviewerPrompt`
156
+ - Output contracts:
157
+ - coder primary: `buildCoderScopeSchema` / `validateCoderScopePayload`
158
+ - coder response: `buildCoderResponseSchema` / `validateCoderResponsePayload`
159
+ - reviewer: `buildReviewerSchema` / `ReviewerPayload`
160
+ - provider surfaces: coder primary/response use `coder_structured_schema`; reviewer uses `structured_advisor_schema`
161
+ - Convergence rule:
162
+ - settle when the reviewer returns no blocking findings and
163
+ `meaningfulProgressAction === accept`, including top-level scopes already
164
+ satisfied by prior accepted work (an eligible already-satisfied
165
+ `advance_parent` is downgraded to `accept` with an explanatory rationale)
166
+ - `advance_parent` is a distinct empty-derived-scope parent-advancement
167
+ signal: it settles by finalizing the parent objective only when
168
+ deterministic classification finds it eligible; an eligible empty-derived
169
+ classification can also upgrade a findings-free `block_for_operator` to
170
+ `advance_parent`
171
+ - revise when findings reopen `coder_response` or `coder_optional_response`
172
+ - block when reviewer returns `block_for_operator`, `replace_plan`, or an
173
+ unsafe `advance_parent`, or coder returns structured `action=blocked` /
174
+ `action=split_plan`; round code still renders compatibility markers for
175
+ downstream state
176
+ - Transition targets: `accept_scope`, `request_revision`, `optional_revision`, `block_for_operator`, `replace_plan`, `advance_parent`
177
+ - Public review note:
178
+ - `neal review` external ranges use the contract-only read-only
179
+ review-findings loop under `.neal/reviews/<review-id>/`, not the execute
180
+ writer-run adjudication family.
181
+
182
+ ### `final_completion_review`
183
+
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 Neal can read that range
186
+ - Prompt surfaces:
187
+ - coder: `completion_coder.final_completion` via `buildFinalCompletionSummaryPrompt`
188
+ - reviewer: `completion_reviewer.final_completion` via `buildFinalCompletionReviewerPrompt`
189
+ - Output contracts:
190
+ - coder: `buildFinalCompletionSummarySchema` / `parseFinalCompletionSummaryPayload`
191
+ - reviewer: `buildFinalCompletionReviewerSchema` / `parseFinalCompletionReviewerPayload`
192
+ - provider surfaces: coder and reviewer both use `structured_advisor_schema`
193
+ - Convergence rule:
194
+ - settle when reviewer returns one of the three whole-plan decisions
195
+ - no coder-response revision round exists; `continue_execution` hands control
196
+ back to execute transitions rather than mutating the adjudicator loop
197
+ - Transition targets: `accept_complete`, `continue_execution`, `block_for_operator`
198
+ - Aggregate review context:
199
+ - `buildFinalCompletionPacket()` derives the aggregate range from the run's
200
+ `initialBaseCommit` and resolved final commit, then records commit
201
+ subjects, diff stat, and changed files when available
202
+ - if the aggregate range cannot be read, the packet records an explicit
203
+ unavailable reason; the reviewer prompt treats that as a completion-review
204
+ evidence gap rather than proof of correctness
205
+ - the final completion reviewer shares the adversarial falsification,
206
+ verification-skepticism, regression-preservation, and
207
+ pre-existing-failure acceptance-surface doctrine from
208
+ `src/neal/prompts/review-doctrine.ts`, but keeps the final-completion
209
+ verdict schema instead of the ordinary scope-review findings schema
210
+
211
+ ## Contract-Only Read-Only Loops
212
+
213
+ ### `review`
214
+
215
+ - Current loop surface: `runNealReviewCli` in `src/neal/review-findings/run.ts`
216
+ - Artifact under review: findings draft for a selected local commit range
217
+ - Prompt surfaces (own prompts, not shared with `scope_reviewer` or
218
+ `review-doctrine.ts`): `buildReviewFindingsDraftPrompt`,
219
+ `buildReviewFindingsReviewPrompt` in `src/neal/review-findings/prompts.ts`
220
+ - Output contracts:
221
+ - draft: `ReviewFindingsDraft`
222
+ - review: `ReviewFindingsReview` with verdict `accepted`, `revise`, or `blocked`
223
+ - Convergence rule (resolved through `resolveReviewedDraftLoopStep`):
224
+ - settle when reviewer returns `accepted` with final markdown
225
+ - revise when reviewer returns concrete findings
226
+ - block when reviewer returns `blocked`, provider validation fails, or
227
+ protected writer state changes; `cap_reached` when the configured
228
+ review-round cap is reached without acceptance
229
+ - Loop contract (`REVIEW_FINDINGS_ADJUDICATED_LOOP_CONTRACT` in `contracts.ts`):
230
+ - loop kind: `review`
231
+ - side-effect policy: `read_only`
232
+ - round cap source: `neal.max_review_rounds`
233
+ - terminal artifact: `.neal/reviews/<review-id>/REVIEW_FINAL.md`
234
+ - State ownership:
235
+ - review artifacts live under `.neal/reviews/<review-id>/`
236
+ - review never becomes a writer-run command path; a read-only guard asserts
237
+ that `.neal/current.json`, `.neal/current-queue.json`, and run-local
238
+ `RUN_STATE.json` files are unchanged after the loop
239
+
240
+ ## Blocked Adjudicator
241
+
242
+ `src/neal/adjudicator/blocked-adjudicator.ts` (`runBlockedAdjudicator`) is a
243
+ read-only triage step for blocked writer runs. It decides whether a block is an
244
+ autonomously recoverable misunderstanding — resolvable within the existing
245
+ scope with no new authorization, external state, or scope expansion — or a
246
+ genuine wall (`authorization`, `external_precondition`, `impossible_task`)
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 `BlockedAdjudicatorVerdict`.
249
+
250
+ Gates, enforced at the recovery chokepoint
251
+ (`src/neal/orchestrator/phases/recovery.ts`); any failed gate falls through to
252
+ generic recovery with no adjudicator invocation:
253
+
254
+ - Source-phase eligibility: `ADJUDICATOR_ELIGIBLE_SOURCE_PHASES` is
255
+ `reviewer_scope`, `reviewer_plan`, `coder_scope`, `coder_response`,
256
+ `coder_optional_response`. Reviewer phases are eligible only for structural
257
+ `review_stuck:` reasons; any coder block on the coder phases is eligible.
258
+ - Per-scope budget: `state.reviewStuckArbiterCount`, bounded by
259
+ `neal.review_stuck_arbiter_max_attempts` (default `1`; `0` disables) and
260
+ reset to `0` at scope boundaries. One invocation consumes one unit whether it
261
+ auto-acts (unattended) or only produces advice (attended, where the verdict
262
+ is surfaced as operator advice and never auto-applied).
263
+ - Anti-thrash window: a block that repeats a `state.recentBlocks` record with
264
+ the same scope identity (`scopeNumber` + `derivedScopeIndex`), the same
265
+ `sourcePhase`, and the same normalized blocker key — with no new evidence —
266
+ short-circuits to a non-recoverable `impossible_task` verdict without
267
+ running an LLM round. The chokepoint is the sole writer of
268
+ `state.recentBlocks`; the adjudicator module's window helpers are pure.
269
+
270
+ Verdict schema constraints (`buildBlockedAdjudicatorSchema` /
271
+ `validateBlockedAdjudicatorVerdictPayload` in `src/neal/agents/schemas.ts`):
272
+ `recoverable=true` is valid only with `triageCategory=misunderstanding` plus a
273
+ non-empty `resolutionDirective`; `recoverable=false` must not pair with
274
+ `misunderstanding`; `rationale` must be non-empty; `targetCanonicalIds` is
275
+ optional and defaults to `[]`. A malformed verdict is rejected so it can never
276
+ drive an autonomous recovery.
277
+
278
+ Prompt and round: `buildBlockedAdjudicatorPrompt` in
279
+ `src/neal/agents/prompts.ts` (a single no-read-safe variant that judges
280
+ entirely from Neal-inlined context) run through `runBlockedAdjudicatorRound`
281
+ in `src/neal/agents/rounds.ts`.
282
+
283
+ ## Adjacent Or Non-Adjudicator Flows
284
+
285
+ - `interactive_blocked_recovery` (declared in `ADJUDICATION_ADJACENT_FLOWS`):
286
+ - current entrypoint: `runInteractiveBlockedRecoveryPhase`
287
+ - reason: operator input, coder dispositions, and recovery routing mix rather
288
+ than forming a pure coder/reviewer adjudication loop, so this remains
289
+ transition-layer-owned
290
+
291
+ ## Spec Family Mapping
292
+
293
+ - Planning family: `plan_review`, `derived_plan_review`
294
+ - Execute family: `execute_review` (meaningful-progress remains a capability of
295
+ this family, not a separate adjudication spec)
296
+ - Final-completion family: `final_completion_review`
297
+ - Contract-only read-only loops: `review`
298
+ - Adjacent read-only triage: the blocked adjudicator (not a spec)
299
+
300
+ 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
+ schemas, allowed transition outcomes, and artifact/context packets remain
303
+ family-specific.
304
+
305
+ New loop variants should extend the adjudication-spec family or remain
306
+ explicitly adjacent; they should not reintroduce bespoke coder/reviewer loop
307
+ wiring inside `src/neal/orchestrator.ts`. When adding or changing a spec, keep
308
+ `transitionSignals`, the family runtime contract, and the loop contract in
309
+ sync — `specs.ts` and `contracts.ts` validate all three at import time, so
310
+ drift fails fast in tests and at startup.