@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.
Files changed (120) hide show
  1. package/README.md +206 -209
  2. package/SECURITY.md +16 -19
  3. package/dist/neal/adjudicator/{blocked-adjudicator.js → consultant.js} +21 -21
  4. package/dist/neal/adjudicator/execute.js +33 -33
  5. package/dist/neal/adjudicator/final-completion.js +2 -31
  6. package/dist/neal/adjudicator/planning.js +264 -24
  7. package/dist/neal/agents/prompts.js +7 -15
  8. package/dist/neal/agents/rounds.js +51 -73
  9. package/dist/neal/agents/schemas.js +615 -502
  10. package/dist/neal/agents/structured-json.js +36 -0
  11. package/dist/neal/blocked-guidance.js +16 -9
  12. package/dist/neal/cli.js +2 -2
  13. package/dist/neal/commands/check.js +2 -2
  14. package/dist/neal/commands/compat.js +100 -36
  15. package/dist/neal/commands/interactive-activity.js +2 -2
  16. package/dist/neal/commands/recovery-guidance.js +20 -8
  17. package/dist/neal/commands/runtime.js +4 -2
  18. package/dist/neal/config.js +40 -7
  19. package/dist/neal/context/inline-review-context.js +13 -28
  20. package/dist/neal/context/reviewer-context.js +19 -14
  21. package/dist/neal/eval/reviewer-eval-manifest.js +86 -0
  22. package/dist/neal/eval/reviewer-eval.js +155 -0
  23. package/dist/neal/git.js +9 -3
  24. package/dist/neal/interactive-controls.js +5 -0
  25. package/dist/neal/orchestrator/completion.js +215 -113
  26. package/dist/neal/orchestrator/failures.js +7 -0
  27. package/dist/neal/orchestrator/notifications.js +9 -9
  28. package/dist/neal/orchestrator/phases/planning.js +124 -112
  29. package/dist/neal/orchestrator/phases/recovery.js +151 -115
  30. package/dist/neal/orchestrator/phases/review.js +28 -1
  31. package/dist/neal/orchestrator/split-plan.js +12 -11
  32. package/dist/neal/orchestrator/transitions.js +36 -71
  33. package/dist/neal/orchestrator.js +3 -0
  34. package/dist/neal/plan-doc.js +24 -1
  35. package/dist/neal/plan-queue.js +29 -1
  36. package/dist/neal/prompts/assert-builder.js +8 -1
  37. package/dist/neal/prompts/execute.js +11 -19
  38. package/dist/neal/prompts/planning.js +64 -55
  39. package/dist/neal/prompts/review-doctrine.js +13 -50
  40. package/dist/neal/prompts/specialized.js +28 -22
  41. package/dist/neal/prompts/specs.js +223 -17
  42. package/dist/neal/providers/agent-settings-isolation.js +35 -0
  43. package/dist/neal/providers/anthropic-claude.js +309 -248
  44. package/dist/neal/providers/detection.js +3 -7
  45. package/dist/neal/providers/git-config-isolation.js +83 -0
  46. package/dist/neal/providers/openai-codex.js +102 -204
  47. package/dist/neal/providers/{generic-agentic-tools.js → openai-compatible-tools.js} +11 -5
  48. package/dist/neal/providers/openai-compatible.js +1079 -403
  49. package/dist/neal/providers/openrouter-routing.js +68 -0
  50. package/dist/neal/providers/pricing.js +124 -0
  51. package/dist/neal/providers/rate-card.js +2275 -0
  52. package/dist/neal/providers/registry.js +6 -17
  53. package/dist/neal/providers/telemetry.js +4 -0
  54. package/dist/neal/providers/types.js +12 -0
  55. package/dist/neal/resume-decision.js +7 -6
  56. package/dist/neal/resume-planner.js +9 -0
  57. package/dist/neal/retrospective.js +36 -7
  58. package/dist/neal/review-debt.js +30 -0
  59. package/dist/neal/review-findings/provider.js +17 -29
  60. package/dist/neal/review.js +17 -1
  61. package/dist/neal/run-metrics.js +74 -9
  62. package/dist/neal/state-invariants.js +11 -3
  63. package/dist/neal/state-views.js +27 -5
  64. package/dist/neal/state.js +59 -15
  65. package/dist/neal/status.js +28 -1
  66. package/dist/neal/support.js +3 -3
  67. package/docs/README.md +22 -18
  68. package/docs/{ADJUDICATOR_INVENTORY.md → adjudicator-inventory.md} +58 -57
  69. package/docs/architecture.md +79 -83
  70. package/docs/automation.md +11 -11
  71. package/docs/compat.md +91 -61
  72. package/docs/compatible-models.md +145 -110
  73. package/docs/demo.md +15 -5
  74. package/docs/maintenance.md +74 -32
  75. package/docs/plan-format.md +61 -33
  76. package/docs/prompt-evals.md +96 -0
  77. package/docs/{PROMPT_SPECS.md → prompt-specs.md} +52 -48
  78. package/docs/providers.md +330 -283
  79. package/docs/release.md +43 -40
  80. package/docs/review-convergence.md +246 -0
  81. package/docs/state-machine.md +61 -59
  82. package/docs/storage.md +40 -31
  83. package/docs/troubleshooting.md +32 -29
  84. package/examples/compat/add-edit-verify/PLAN.md +2 -0
  85. package/examples/compat/add-edit-verify/broken.diff +2 -2
  86. package/examples/compat/add-edit-verify/good.diff +2 -2
  87. package/examples/compat/add-edit-verify/src/add.js +1 -1
  88. package/examples/compat/manifest.json +25 -29
  89. package/examples/compat/sum-grep-edit/PLAN.md +33 -0
  90. package/examples/compat/sum-grep-edit/broken.diff +12 -0
  91. package/examples/compat/sum-grep-edit/good.diff +12 -0
  92. package/examples/compat/sum-grep-edit/package.json +5 -0
  93. package/examples/compat/sum-grep-edit/src/numbers.js +9 -0
  94. package/examples/compat/sum-grep-edit/test/numbers.test.js +15 -0
  95. package/examples/issue-triage-js/README.md +0 -15
  96. package/examples/reviewer-eval/clean-doc/base/src/clamp.ts +3 -0
  97. package/examples/reviewer-eval/clean-doc/change.diff +9 -0
  98. package/examples/reviewer-eval/clean-extract/base/src/greet.ts +3 -0
  99. package/examples/reviewer-eval/clean-extract/change.diff +13 -0
  100. package/examples/reviewer-eval/dropped-error/base/src/load.ts +4 -0
  101. package/examples/reviewer-eval/dropped-error/change.diff +15 -0
  102. package/examples/reviewer-eval/manifest.json +45 -0
  103. package/examples/reviewer-eval/off-by-one/base/src/paginate.ts +5 -0
  104. package/examples/reviewer-eval/off-by-one/change.diff +11 -0
  105. package/{config.yml → neal.yml} +1 -1
  106. package/package.json +20 -15
  107. package/dist/neal/providers/generic-agentic.js +0 -1238
  108. package/docs/comparison.md +0 -105
  109. package/examples/compat/is-even-add-test/PLAN.md +0 -30
  110. package/examples/compat/is-even-add-test/broken.diff +0 -11
  111. package/examples/compat/is-even-add-test/good.diff +0 -11
  112. package/examples/compat/is-even-add-test/package.json +0 -5
  113. package/examples/compat/is-even-add-test/src/is-even.js +0 -3
  114. package/examples/compat/is-even-add-test/test/is-even.test.js +0 -9
  115. package/examples/compat/reverse-grep-edit/PLAN.md +0 -32
  116. package/examples/compat/reverse-grep-edit/broken.diff +0 -12
  117. package/examples/compat/reverse-grep-edit/good.diff +0 -12
  118. package/examples/compat/reverse-grep-edit/package.json +0 -5
  119. package/examples/compat/reverse-grep-edit/src/strings.js +0 -10
  120. package/examples/compat/reverse-grep-edit/test/strings.test.js +0 -15
@@ -2,6 +2,37 @@ import { aggregateChangedFilesForAcceptedDerivedScopes, getAcceptedDerivedScopes
2
2
  import { toResidualReviewDebt } from '../review-debt.js';
3
3
  import { getDerivedPlanView, getFinalCompletionView } from '../state-views.js';
4
4
  const ALREADY_SATISFIED_ACCEPTANCE_RATIONALE_PREFIX = 'Accepted top-level already-satisfied scope';
5
+ export function createScopeBoundaryReset() {
6
+ return {
7
+ currentScopeProgressJustification: null,
8
+ currentScopeMeaningfulProgressVerdict: null,
9
+ rounds: [],
10
+ recentBlocks: [],
11
+ consultantAttemptCount: 0,
12
+ findings: [],
13
+ // planReviewDebt is a projection of the current findings, so clearing
14
+ // findings must clear it too (toPlanReviewDebt([]) === []); otherwise a new
15
+ // derived-plan negotiation would carry stale current-negotiation debt. The
16
+ // durable inheritedPlanReviewDebt is intentionally NOT reset here — it is
17
+ // write-once and survives scope boundaries.
18
+ planReviewDebt: [],
19
+ createdCommits: [],
20
+ };
21
+ }
22
+ export function createNextScopeEntryReset(baseCommit) {
23
+ return {
24
+ ...createScopeBoundaryReset(),
25
+ baseCommit,
26
+ finalCommit: null,
27
+ archivedReviewPath: null,
28
+ coderSessionHandle: null,
29
+ coderSessionProtocol: null,
30
+ lastScopeMarker: null,
31
+ finalCompletionSummary: null,
32
+ phase: 'coder_scope',
33
+ status: 'running',
34
+ };
35
+ }
5
36
  export function appendCompletedScope(state, result, details) {
6
37
  const scopeLabel = details.scopeLabel ?? getCurrentScopeLabel(state);
7
38
  const marker = details.marker ?? (state.lastScopeMarker ?? 'AUTONOMY_BLOCKED');
@@ -57,23 +88,17 @@ export function adoptAcceptedDerivedPlan(state) {
57
88
  }
58
89
  return {
59
90
  ...state,
91
+ ...createScopeBoundaryReset(),
60
92
  phase: 'coder_scope',
61
93
  status: 'running',
62
94
  derivedScopeIndex: derivedPlan.scopeIndex ?? 1,
63
95
  coderSessionHandle: null,
64
96
  coderSessionProtocol: null,
65
97
  coderRetryCount: 0,
66
- currentScopeProgressJustification: null,
67
- currentScopeMeaningfulProgressVerdict: null,
68
98
  finalCompletionSummary: null,
69
99
  finalCompletionReviewVerdict: null,
70
100
  finalCompletionResolvedAction: null,
71
101
  finalCompletionContinueExecutionCapReached: false,
72
- rounds: [],
73
- recentBlocks: [],
74
- reviewStuckArbiterCount: 0,
75
- findings: [],
76
- createdCommits: [],
77
102
  blockedFromPhase: null,
78
103
  };
79
104
  }
@@ -89,15 +114,8 @@ export function computeNextScopeStateAfterExecuteFinalization({ state, finalComm
89
114
  if (derivedExecution && derivedPlanCompleted) {
90
115
  return {
91
116
  ...state,
92
- baseCommit: finalCommit,
93
- finalCommit: null,
94
- coderSessionHandle: null,
95
- coderSessionProtocol: null,
117
+ ...createNextScopeEntryReset(finalCommit),
96
118
  currentScopeNumber: nextTopLevelScopeNumber,
97
- lastScopeMarker: null,
98
- currentScopeProgressJustification: null,
99
- currentScopeMeaningfulProgressVerdict: null,
100
- finalCompletionSummary: null,
101
119
  finalCompletionReviewVerdict: null,
102
120
  finalCompletionResolvedAction: null,
103
121
  finalCompletionContinueExecutionCapReached: false,
@@ -109,28 +127,13 @@ export function computeNextScopeStateAfterExecuteFinalization({ state, finalComm
109
127
  derivedPlanAcceptedNotified: false,
110
128
  splitPlanBlockedNotified: false,
111
129
  splitPlanCountForCurrentScope: 0,
112
- rounds: [],
113
- recentBlocks: [],
114
- reviewStuckArbiterCount: 0,
115
- findings: [],
116
- createdCommits: [],
117
130
  completedScopes,
118
- archivedReviewPath: null,
119
- phase: 'coder_scope',
120
- status: 'running',
121
131
  };
122
132
  }
123
133
  if (derivedExecution) {
124
134
  return {
125
135
  ...state,
126
- baseCommit: finalCommit,
127
- finalCommit: null,
128
- coderSessionHandle: null,
129
- coderSessionProtocol: null,
130
- lastScopeMarker: null,
131
- currentScopeProgressJustification: null,
132
- currentScopeMeaningfulProgressVerdict: null,
133
- finalCompletionSummary: null,
136
+ ...createNextScopeEntryReset(finalCommit),
134
137
  finalCompletionReviewVerdict: null,
135
138
  finalCompletionResolvedAction: null,
136
139
  finalCompletionContinueExecutionCapReached: false,
@@ -138,29 +141,14 @@ export function computeNextScopeStateAfterExecuteFinalization({ state, finalComm
138
141
  splitPlanStartedNotified: false,
139
142
  derivedPlanAcceptedNotified: false,
140
143
  splitPlanBlockedNotified: false,
141
- rounds: [],
142
- recentBlocks: [],
143
- reviewStuckArbiterCount: 0,
144
- findings: [],
145
- createdCommits: [],
146
144
  completedScopes,
147
- archivedReviewPath: null,
148
- phase: 'coder_scope',
149
- status: 'running',
150
145
  };
151
146
  }
152
147
  if (continueScopes) {
153
148
  return {
154
149
  ...state,
155
- baseCommit: finalCommit,
156
- finalCommit: null,
157
- coderSessionHandle: null,
158
- coderSessionProtocol: null,
150
+ ...createNextScopeEntryReset(finalCommit),
159
151
  currentScopeNumber: nextTopLevelScopeNumber,
160
- lastScopeMarker: null,
161
- currentScopeProgressJustification: null,
162
- currentScopeMeaningfulProgressVerdict: null,
163
- finalCompletionSummary: null,
164
152
  finalCompletionReviewVerdict: null,
165
153
  finalCompletionResolvedAction: null,
166
154
  finalCompletionContinueExecutionCapReached: false,
@@ -172,15 +160,7 @@ export function computeNextScopeStateAfterExecuteFinalization({ state, finalComm
172
160
  derivedPlanAcceptedNotified: false,
173
161
  splitPlanBlockedNotified: false,
174
162
  splitPlanCountForCurrentScope: 0,
175
- rounds: [],
176
- recentBlocks: [],
177
- reviewStuckArbiterCount: 0,
178
- findings: [],
179
- createdCommits: [],
180
163
  completedScopes,
181
- archivedReviewPath: null,
182
- phase: 'coder_scope',
183
- status: 'running',
184
164
  };
185
165
  }
186
166
  return {
@@ -223,19 +203,11 @@ export function computeNextScopeStateAfterParentAdvance(args) {
223
203
  : args.state.currentScopeNumber;
224
204
  return {
225
205
  ...args.state,
226
- baseCommit: args.finalCommit,
227
- finalCommit: null,
228
- archivedReviewPath: null,
229
- coderSessionHandle: null,
230
- coderSessionProtocol: null,
206
+ ...createNextScopeEntryReset(args.finalCommit),
231
207
  reviewerSessionHandle: null,
232
208
  coderRetryCount: 0,
233
209
  currentScopeNumber: nextTopLevelScopeNumber,
234
- lastScopeMarker: null,
235
- currentScopeProgressJustification: null,
236
- currentScopeMeaningfulProgressVerdict: null,
237
210
  manualGate: null,
238
- finalCompletionSummary: null,
239
211
  finalCompletionReviewVerdict: null,
240
212
  finalCompletionResolvedAction: null,
241
213
  finalCompletionContinueExecutionCapReached: false,
@@ -247,16 +219,9 @@ export function computeNextScopeStateAfterParentAdvance(args) {
247
219
  derivedPlanAcceptedNotified: false,
248
220
  splitPlanBlockedNotified: false,
249
221
  splitPlanCountForCurrentScope: 0,
250
- rounds: [],
251
- recentBlocks: [],
252
- reviewStuckArbiterCount: 0,
253
- findings: [],
254
- createdCommits: [],
255
222
  completedScopes: args.completedScopes,
256
223
  blockedFromPhase: null,
257
224
  interactiveBlockedRecovery: null,
258
- phase: 'coder_scope',
259
- status: 'running',
260
225
  };
261
226
  }
262
227
  function getAcceptedScopeSummary(state) {
@@ -81,6 +81,9 @@ export async function initializeOrchestration(planDoc, cwd, agentConfig, topLeve
81
81
  reviewMarkdownPath: join(logger.runDir, 'REVIEW.md'),
82
82
  recoveryMarkdownPath: join(logger.runDir, 'RECOVERY.md'),
83
83
  maxRounds: getMaxReviewRounds(cwd),
84
+ // Only the `neal run` queue's execution stage supplies this; every other
85
+ // caller omits it and the durable inherited debt seeds to an empty array.
86
+ inheritedPlanReviewDebt: options?.inheritedPlanReviewDebt,
84
87
  };
85
88
  await mkdir(stateDir, { recursive: true });
86
89
  const baseCommit = await getHeadCommit(cwd);
@@ -1,6 +1,29 @@
1
- import { realpath, stat } from 'node:fs/promises';
1
+ import { readFile, realpath, stat, writeFile } from 'node:fs/promises';
2
2
  import { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep } from 'node:path';
3
3
  import { getIgnoredPaths, getRepositoryRoot } from './git.js';
4
+ /**
5
+ * Run a destructive worktree action (reset --hard / clean) while preserving
6
+ * the plan document's byte content across it. The plan doc is a wrapper-owned
7
+ * overlay by contract: it may be an uncommitted modification of a TRACKED file
8
+ * (reset --hard would silently restore the committed content — a different
9
+ * plan — mid-run) or an untracked file (clean would delete it). Either way the
10
+ * run's plan must survive worktree hygiene. Restores nothing if the plan doc
11
+ * did not exist before the action.
12
+ */
13
+ export async function withPlanDocPreserved(planDoc, action) {
14
+ let content = null;
15
+ try {
16
+ content = await readFile(planDoc);
17
+ }
18
+ catch {
19
+ content = null;
20
+ }
21
+ const result = await action();
22
+ if (content !== null) {
23
+ await writeFile(planDoc, content);
24
+ }
25
+ return result;
26
+ }
4
27
  export async function inspectPlanDocDisposition(cwd, planDoc, options = {}) {
5
28
  const repositoryRoot = await realpath(await getRepositoryRoot(cwd));
6
29
  const absolutePlanDoc = resolve(cwd, planDoc);
@@ -9,7 +9,7 @@ import { writeExecutionArtifacts } from './orchestrator/artifacts.js';
9
9
  import { assertAgentConfigSupportsResume, assertAgentConfigSupportsWriterRun } from './providers/registry.js';
10
10
  import { writeCurrentRunPointer } from './run-registry.js';
11
11
  import { formatPublicRunStatus, getRunDisplayStatus } from './run-status.js';
12
- import { getRunStatePath, loadState, saveState } from './state.js';
12
+ import { getRunStatePath, hydrateResidualReviewDebtItem, loadState, saveState } from './state.js';
13
13
  import { QUEUE_LINK_FILE, QUEUE_STATE_FILE, QUEUE_SUMMARY_FILE, getCurrentPlanAndExecuteQueuePointerPath as getStorageCurrentPlanAndExecuteQueuePointerPath, getQueuesDir, } from './storage-paths.js';
14
14
  import { executeRun, withPreparedWriterRun, } from './commands/runtime.js';
15
15
  import { filterAllowedDirtyPathStatus, filterWrapperOwnedWorktreeStatus, parseWorktreeStatusLine, } from './worktree-status.js';
@@ -114,6 +114,7 @@ export async function createPlanAndExecuteQueue(args) {
114
114
  planningRunId: null,
115
115
  planningStatePath: null,
116
116
  acceptedPlanPath: null,
117
+ planReviewDebt: [],
117
118
  executionRunId: null,
118
119
  executionStatePath: null,
119
120
  activeStage: null,
@@ -496,6 +497,10 @@ async function runFreshPlanAndExecuteChild(args, onInitialized) {
496
497
  // initialized, the run state is the single source of truth for
497
498
  // this run — `neal resume` reads it back from there.
498
499
  autoSquashOnCompletion: args.squashOnCompletion,
500
+ // Only the execution child inherits the accepted plan's plan-review
501
+ // debt (into the durable inheritedPlanReviewDebt state field). The
502
+ // planning child recomputes its own planReviewDebt from scratch.
503
+ inheritedPlanReviewDebt: args.stage === 'execution' ? args.item.planReviewDebt : undefined,
499
504
  });
500
505
  markInitialized();
501
506
  assertAgentConfigSupportsWriterRun(loaded.state.agentConfig, { context: `${args.stage} queue child` });
@@ -581,6 +586,11 @@ async function completePlanningStage(state, itemIndex, finalState, deps) {
581
586
  ...item,
582
587
  status: 'planned',
583
588
  acceptedPlanPath,
589
+ // Carry the accepted plan's current-negotiation debt onto the queue item
590
+ // so the fresh execution child inherits it (both the in-process completion
591
+ // and the cross-process continuePlanAndExecuteQueueFromChildRun resume
592
+ // route through here).
593
+ planReviewDebt: finalState.planReviewDebt,
584
594
  activeStage: null,
585
595
  stopReason: null,
586
596
  }),
@@ -916,6 +926,10 @@ function parsePlanAndExecuteQueueItem(value, expectedIndex, cwd) {
916
926
  planningRunId: requireNullableString(item.planningRunId, 'Invalid queue state: malformed planningRunId'),
917
927
  planningStatePath,
918
928
  acceptedPlanPath,
929
+ // Legacy-tolerant: queue items persisted before this field existed default
930
+ // to an empty debt array. Present arrays are hydrated with the shared
931
+ // residual-debt validator so an older/newer field shape is caught.
932
+ planReviewDebt: parseQueueItemPlanReviewDebt(item.planReviewDebt),
919
933
  executionRunId: requireNullableString(item.executionRunId, 'Invalid queue state: malformed executionRunId'),
920
934
  executionStatePath,
921
935
  activeStage: requireNullableQueueChildStage(item.activeStage, 'Invalid queue state: malformed activeStage'),
@@ -924,6 +938,20 @@ function parsePlanAndExecuteQueueItem(value, expectedIndex, cwd) {
924
938
  stopReason: requireNullableString(item.stopReason, 'Invalid queue state: malformed item stopReason'),
925
939
  };
926
940
  }
941
+ function parseQueueItemPlanReviewDebt(value) {
942
+ // Legacy-tolerant ONLY for a genuinely absent field: queue items persisted
943
+ // before this field existed omit it entirely (undefined), and those default to
944
+ // an empty debt array. A PRESENT null (or any other non-array) is corrupted
945
+ // state that would silently erase accepted plan-review debt, so it is rejected
946
+ // as malformed rather than defaulted away.
947
+ if (value === undefined) {
948
+ return [];
949
+ }
950
+ if (!Array.isArray(value)) {
951
+ throw new Error('Invalid queue state: malformed planReviewDebt');
952
+ }
953
+ return value.map((item, index) => hydrateResidualReviewDebtItem(item, `queue item planReviewDebt[${index}]`));
954
+ }
927
955
  function parseCurrentPlanAndExecuteQueuePointer(value, cwd) {
928
956
  if (!value || typeof value !== 'object') {
929
957
  throw new Error('Invalid .neal/current-queue.json: expected object');
@@ -1,4 +1,4 @@
1
- import { getPromptSpec } from './specs.js';
1
+ import { getPromptSpec, } from './specs.js';
2
2
  export function assertPromptBuilder(id, exportName, modulePath) {
3
3
  const spec = getPromptSpec(id);
4
4
  const allowedBuilders = [spec.baseInstructions, ...spec.variants.map((variant) => variant.baseInstructions)];
@@ -11,3 +11,10 @@ export function assertPromptBuilder(id, exportName, modulePath) {
11
11
  }
12
12
  return spec;
13
13
  }
14
+ export function resolvePrimaryVariant(spec, specLabel) {
15
+ const primary = spec.variants.find((variant) => variant.kind === 'primary');
16
+ if (!primary) {
17
+ throw new Error(`Prompt spec ${specLabel} is missing a primary variant`);
18
+ }
19
+ return primary;
20
+ }
@@ -1,9 +1,8 @@
1
- import { assertNoReadPromptInstructionText, renderInlineReviewerContext, renderInlinedRangeDiffSection, } from '../context/inline-review-context.js';
2
- import { renderReviewerContextMarkdown } from '../context/reviewer-context.js';
1
+ import { renderInlinedRangeDiffSection } from '../context/inline-review-context.js';
3
2
  import { AUTONOMY_BLOCKED, AUTONOMY_DONE, AUTONOMY_SCOPE_DONE, AUTONOMY_SPLIT_PLAN, buildProgressSection, getCanonicalPlanContractLines, getDerivedPlanSectionContractLines, getExecuteScopeProgressPayloadContractLines, getProtocolMarkerArtifactProhibitionLines, getStandalonePlanPayloadSourceOfTruthLines, getTerminalMarkerArtifactBoundaryLines, getUnattendedAutonomyLines, } from './shared.js';
4
3
  import { assertPromptBuilder } from './assert-builder.js';
5
4
  import { getUserGuidanceLines } from './guidance.js';
6
- import { getAdversarialReviewDoctrineLines, getCodeReviewFalsificationLines, getFindingQualityLines, getPreexistingFailureContractLines, getRegressionPreservationLines, getVerificationSkepticismLines, resolveReviewerPromptAccessMode, } from './review-doctrine.js';
5
+ import { getAdversarialReviewDoctrineLines, getCodeReviewFalsificationLines, getFindingQualityLines, getPreexistingFailureContractLines, getRegressionPreservationLines, getVerificationSkepticismLines, } from './review-doctrine.js';
7
6
  const PROMPT_MODULE_PATH = 'src/neal/prompts/execute.ts';
8
7
  export const CODER_REGRESSION_PRESERVATION_LINE = 'Treat existing behavior on the code paths you touch as part of the contract: identify the shared subsystems your change intersects (state, parsing, dispatch, lifecycle, storage, startup) and confirm that adjacent behavior on those paths still works — by running existing tests that exercise them when such tests exist, or by concrete reasoning through representative flows otherwise — before you finish.';
9
8
  export const CODER_PREEXISTING_FAILURE_LINES = [
@@ -54,6 +53,7 @@ export function buildScopePrompt(planDoc, progressText, options) {
54
53
  CODER_REGRESSION_PRESERVATION_LINE,
55
54
  ...CODER_PREEXISTING_FAILURE_LINES,
56
55
  'Verify the relevant work before you finish.',
56
+ 'Before claiming a step is done or a verification passed, confirm the claim against an actual tool or command result from this session, and state plainly when a step is not yet verified.',
57
57
  'Create real git commit(s) for completed work.',
58
58
  'Do not edit or stage wrapper-owned artifacts such as review files under .neal/runs/, PLAN_PROGRESS.md, plan-progress.json, or .neal/*.',
59
59
  'Treat action=`blocked` as a last resort, not an early exit.',
@@ -114,12 +114,7 @@ export function buildReviewerPrompt(args) {
114
114
  if (!primaryVariant || !meaningfulProgressVariant) {
115
115
  throw new Error('Prompt spec scope_reviewer is missing primary or meaningful-progress coverage');
116
116
  }
117
- const accessMode = resolveReviewerPromptAccessMode({
118
- builder: 'buildReviewerPrompt',
119
- accessMode: args.accessMode,
120
- inlineContextSupplied: Boolean(args.inlineContext),
121
- });
122
- const noRead = accessMode === 'no-read';
117
+ const accessMode = args.accessMode ?? 'tool-access';
123
118
  // A collected diff may legitimately be the empty string (a range with no
124
119
  // changes); distinguish "collected" (any string, including '') from "not
125
120
  // collected" (null/undefined) so an empty diff still rides the inlined channel
@@ -157,12 +152,7 @@ export function buildReviewerPrompt(args) {
157
152
  reviewTarget: 'scope diff',
158
153
  mode: accessMode,
159
154
  });
160
- const reviewHistoryLine = noRead
161
- ? 'Prior review history is inlined below if you need earlier reviewer findings or coder responses, but review the inlined diff for the current commit range directly.'
162
- : `Prior review history is available at ${args.reviewMarkdownPath} if you need earlier reviewer findings or coder responses, but review the current commit range directly.`;
163
- if (noRead) {
164
- assertNoReadPromptInstructionText([...falsificationLines, ...skepticismLines, ...regressionLines, ...preexistingLines, reviewHistoryLine].join('\n'), 'buildReviewerPrompt');
165
- }
155
+ const reviewHistoryLine = `Prior review history is available at ${args.reviewMarkdownPath} if you need earlier reviewer findings or coder responses, but review the current commit range directly.`;
166
156
  return [
167
157
  `Review the current scope for plan ${args.planDoc}.`,
168
158
  `Review round: ${args.round}.`,
@@ -191,7 +181,7 @@ export function buildReviewerPrompt(args) {
191
181
  'Changed files:',
192
182
  changedFilesText,
193
183
  '',
194
- ...getReviewerContextLines(args.reviewerContext, noRead ? 'inline' : 'tool-access'),
184
+ ...getReviewerContextLines(args.reviewerContext),
195
185
  `The active parent objective for meaningful-progress evaluation is scope ${args.parentScopeLabel}.`,
196
186
  'You are the authority for meaningful-progress gating during this execute review pass, but perform that judgment after the adversarial correctness review above.',
197
187
  'Set `meaningfulProgressAction` to `accept` only when either this scope materially advances the active parent objective, or a top-level scope has no required new diff because prior accepted work already satisfies it and you find no issues with sufficient verification evidence.',
@@ -215,7 +205,6 @@ export function buildReviewerPrompt(args) {
215
205
  'If prior review history or continuity context describes a finding as fixed, rejected, or deferred, do not reopen the same claim from that history alone.',
216
206
  'Before emitting a finding that resembles a prior fixed/rejected/deferred finding, inspect the current code or current diff and cite fresh current evidence showing the defect still exists.',
217
207
  'If current evidence is ambiguous for an already-addressed claim, do not use a blocking finding to force another coder loop; use meaningfulProgressAction=`block_for_operator` only when operator judgment is genuinely required.',
218
- ...(args.inlineContext ? ['', renderInlineReviewerContext(args.inlineContext)] : []),
219
208
  ...(rangeDiffInlined
220
209
  ? [
221
210
  '',
@@ -227,13 +216,13 @@ export function buildReviewerPrompt(args) {
227
216
  : []),
228
217
  ].join('\n');
229
218
  }
230
- function getReviewerContextLines(reviewerContext, mode = 'tool-access') {
219
+ function getReviewerContextLines(reviewerContext) {
231
220
  if (!reviewerContext) {
232
221
  return [];
233
222
  }
234
223
  return [
235
224
  'Bounded current-run continuity context:',
236
- mode === 'inline' ? renderReviewerContextMarkdown(reviewerContext, { mode: 'inline' }) : reviewerContext.promptMarkdown,
225
+ reviewerContext.promptMarkdown,
237
226
  '',
238
227
  ];
239
228
  }
@@ -257,8 +246,11 @@ export function buildCoderResponsePrompt(args) {
257
246
  mode === 'blocking'
258
247
  ? 'Make code changes if needed, run the most relevant verification for the fixes you make, and create a real git commit if you changed code.'
259
248
  : 'Fix local, concrete, low-expansion non-blocking findings by default. Make the smallest justified code changes, run the most relevant verification for those changes, and create a real git commit if you changed code.',
249
+ // model-calibrated: caps post-commit tool use once the fix is committed and verified; tuned for a model that otherwise keeps issuing redundant shell commands after it already has enough evidence.
260
250
  'After you have made the necessary commit and run the relevant verification, stop using tools. Your next response must return the structured outcome with no additional shell commands such as git show, git status, or extra exploratory tests.',
251
+ // model-calibrated: stops repeated re-verification of an already-evidenced fix; tuned for a model that over-proves rather than summarizing the evidence it already gathered.
261
252
  'Do not keep proving the same fix after you have enough evidence to answer each finding. Summarize the evidence in the structured response instead.',
253
+ 'Before claiming a step is done or a verification passed, confirm the claim against an actual tool or command result from this session, and state plainly when a finding is not yet verified.',
262
254
  CODER_REGRESSION_PRESERVATION_LINE,
263
255
  ...CODER_PREEXISTING_FAILURE_LINES,
264
256
  'Use `fixed` only when you actually changed the code or verification in a way that resolves the finding.',