@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,104 @@
1
+ import { mkdir } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import { runCoderFinalCompletionSummaryRound, runReviewerFinalCompletionRound, } from '../agents.js';
4
+ import { createInlineSection, readOnlyReviewerNeedsInlinedDiff, readTextForInlineSection, reviewerNeedsInlineContext, } from '../context/inline-review-context.js';
5
+ import { buildAndPersistReviewerContextPacket } from '../context/reviewer-context.js';
6
+ import { getDiffForRange } from '../git.js';
7
+ import { getFinalCompletionView } from '../state-views.js';
8
+ import { getFinalCompletionReviewerScratchDir } from '../storage-paths.js';
9
+ import { getAdjudicationSpec } from './specs.js';
10
+ export function resolveFinalCompletionAdjudicationContext(args) {
11
+ const spec = getAdjudicationSpec('final_completion_review');
12
+ if (spec.family !== 'final_completion') {
13
+ throw new Error(`Expected final completion adjudication spec, received ${spec.id}.`);
14
+ }
15
+ return {
16
+ spec,
17
+ packet: args.packet,
18
+ summary: getFinalCompletionView(args.state)?.summary ?? null,
19
+ };
20
+ }
21
+ export async function runFinalCompletionSummaryAdjudication(args) {
22
+ const context = resolveFinalCompletionAdjudicationContext({
23
+ state: args.state,
24
+ packet: args.packet,
25
+ });
26
+ const summary = await (args.runSummaryRound ?? runCoderFinalCompletionSummaryRound)({
27
+ coder: args.state.agentConfig.coder,
28
+ cwd: args.state.cwd,
29
+ planDoc: args.state.planDoc,
30
+ packet: context.packet,
31
+ logger: args.logger,
32
+ });
33
+ return {
34
+ context,
35
+ summary,
36
+ };
37
+ }
38
+ async function buildFinalCompletionReviewerInlineContext(args) {
39
+ const aggregate = args.packet.aggregateReviewContext;
40
+ const sections = [];
41
+ if (aggregate.unavailableReason !== null || !aggregate.baseCommit || !aggregate.headCommit) {
42
+ // Inline the evidence-gap statement instead of a diff so the no-read
43
+ // reviewer treats the missing aggregate range as a completion-review
44
+ // evidence gap rather than proof of correctness.
45
+ sections.push(createInlineSection('Aggregate review evidence gap', `The aggregate diff for this completion review is unavailable: ${aggregate.unavailableReason ?? 'no aggregate commit range was resolved'}. Treat this as a completion-review evidence gap rather than proof that the aggregate implementation is correct.`));
46
+ }
47
+ else {
48
+ const diff = await args.getDiffForRange(args.state.cwd, aggregate.baseCommit, aggregate.headCommit);
49
+ sections.push(createInlineSection(`Full aggregate diff for range ${aggregate.range ?? `${aggregate.baseCommit}..${aggregate.headCommit}`}`, diff || '(empty diff)'));
50
+ }
51
+ const planContent = await readTextForInlineSection(resolve(args.state.cwd, args.state.planDoc));
52
+ sections.push(createInlineSection(`Plan document content (${args.state.planDoc})`, planContent || '(plan document content unavailable)'));
53
+ return { sections };
54
+ }
55
+ export async function runFinalCompletionReviewerAdjudication(args) {
56
+ const finalCompletion = getFinalCompletionView(args.state);
57
+ if (!finalCompletion?.summary) {
58
+ throw new Error('Cannot run final completion reviewer adjudication without a final completion summary.');
59
+ }
60
+ const context = resolveFinalCompletionAdjudicationContext({
61
+ state: args.state,
62
+ packet: args.packet,
63
+ });
64
+ const scratchDir = getFinalCompletionReviewerScratchDir(args.state.runDir);
65
+ await mkdir(scratchDir, { recursive: true });
66
+ // The aggregate diff is collected only for reviewers without repository read
67
+ // access; read-capable reviewers inspect the aggregate range with their own
68
+ // tools.
69
+ const inlineContext = reviewerNeedsInlineContext(args.state.agentConfig.reviewer)
70
+ ? await buildFinalCompletionReviewerInlineContext({
71
+ state: args.state,
72
+ packet: context.packet,
73
+ getDiffForRange: args.getDiffForRange ?? getDiffForRange,
74
+ })
75
+ : null;
76
+ // Read-only reviewers with read tools but no commit-range diff tool (native
77
+ // Claude/Codex) get the aggregate commit-range diff inlined directly when the
78
+ // aggregate range is available; read-only reviewers that expose their own
79
+ // commit-range diff tool (generic-agentic) inspect the range with that tool.
80
+ const aggregate = context.packet.aggregateReviewContext;
81
+ const inlinedRangeDiff = readOnlyReviewerNeedsInlinedDiff(args.state.agentConfig.reviewer) &&
82
+ aggregate.unavailableReason === null &&
83
+ aggregate.baseCommit &&
84
+ aggregate.headCommit
85
+ ? await (args.getDiffForRange ?? getDiffForRange)(args.state.cwd, aggregate.baseCommit, aggregate.headCommit)
86
+ : null;
87
+ const reviewerResult = await (args.runReviewerRound ?? runReviewerFinalCompletionRound)({
88
+ reviewer: args.state.agentConfig.reviewer,
89
+ cwd: args.state.cwd,
90
+ planDoc: args.state.planDoc,
91
+ packet: context.packet,
92
+ summary: finalCompletion.summary,
93
+ scratchDir,
94
+ reviewerContext: await buildAndPersistReviewerContextPacket({ state: args.state }),
95
+ inlineContext,
96
+ inlinedRangeDiff,
97
+ unattended: args.state.unattended,
98
+ logger: args.logger,
99
+ });
100
+ return {
101
+ context,
102
+ reviewerResult,
103
+ };
104
+ }
@@ -0,0 +1,145 @@
1
+ import { resolve } from 'node:path';
2
+ import { runCoderPlanResponseRound, runPlanReviewerRound } from '../agents.js';
3
+ import { createInlineSection, readTextForInlineSection, reviewerNeedsInlineContext, } from '../context/inline-review-context.js';
4
+ import { buildAndPersistReviewerContextPacket } from '../context/reviewer-context.js';
5
+ import { getProviderDefinition } from '../providers/registry.js';
6
+ import { getDerivedPlanView } from '../state-views.js';
7
+ import { getAdjudicationSpec } from './specs.js';
8
+ export function isDerivedPlanReviewState(state) {
9
+ return getDerivedPlanView(state)?.reviewActive ?? false;
10
+ }
11
+ // The planner role runs through the coder adapter, so its session-resume capability comes from
12
+ // the provider's coder capabilities. Providers that declare `supportsSessionResume: false`
13
+ // (openai-compatible, generic-agentic) never persist a planner session handle — a null handle is
14
+ // their expected steady state, so top-level plan-refinement responses must start a fresh planner
15
+ // session for them instead of treating the missing handle as corrupted state. Resume-capable
16
+ // planner providers keep the hard invariant: a missing handle there is a genuine violation.
17
+ export function plannerProviderStartsFreshSessions(planner) {
18
+ return !getProviderDefinition(planner.provider).capabilities.coder.supportsSessionResume;
19
+ }
20
+ export function resolvePlanningAdjudicationContext(state) {
21
+ const derivedPlan = getDerivedPlanView(state);
22
+ const derivedPlanReview = derivedPlan?.reviewActive ?? false;
23
+ const reviewMode = derivedPlanReview ? 'derived-plan' : 'plan';
24
+ const spec = getAdjudicationSpec(reviewMode === 'derived-plan'
25
+ ? 'derived_plan_review'
26
+ : 'plan_review');
27
+ if (spec.family !== 'plan_review') {
28
+ throw new Error(`Expected planning adjudication spec, received ${spec.id}.`);
29
+ }
30
+ return {
31
+ spec,
32
+ reviewMode,
33
+ derivedPlanReview,
34
+ reviewTargetPath: derivedPlanReview && derivedPlan
35
+ ? derivedPlan.path
36
+ : state.planDoc,
37
+ roundLimit: derivedPlanReview && derivedPlan ? derivedPlan.counters.maxDerivedPlanReviewRounds : state.maxRounds,
38
+ parentPlanDoc: derivedPlanReview ? state.planDoc : undefined,
39
+ derivedFromScopeNumber: derivedPlanReview && derivedPlan ? derivedPlan.parentScopeNumber : null,
40
+ };
41
+ }
42
+ async function buildPlanReviewerInlineContext(args) {
43
+ const planContent = await readTextForInlineSection(resolve(args.state.cwd, args.reviewedPlanPath));
44
+ const sections = [
45
+ createInlineSection(`Reviewed plan document content (${args.reviewedPlanPath})`, planContent || '(plan document content unavailable)'),
46
+ ];
47
+ if (args.context.reviewMode === 'derived-plan' && args.context.parentPlanDoc) {
48
+ const parentPlanContent = await readTextForInlineSection(resolve(args.state.cwd, args.context.parentPlanDoc));
49
+ sections.push(createInlineSection(`Parent plan document content (${args.context.parentPlanDoc})`, parentPlanContent || '(parent plan document content unavailable)'));
50
+ }
51
+ const reviewHistory = await readTextForInlineSection(resolve(args.state.cwd, args.reviewMarkdownPath));
52
+ sections.push(createInlineSection('Prior review history (REVIEW.md content)', reviewHistory || '(no prior review history)'));
53
+ return { sections };
54
+ }
55
+ export async function runPlanningReviewerAdjudication(args) {
56
+ const context = resolvePlanningAdjudicationContext(args.state);
57
+ const preparedReview = await args.preparePlanReviewArtifact({
58
+ planPath: context.reviewTargetPath,
59
+ normalizedPlanPath: args.normalizedPlanPath,
60
+ });
61
+ // Plan and review-history contents are inlined only for reviewers without
62
+ // repository read access; read-capable reviewers inspect them directly.
63
+ const inlineContext = reviewerNeedsInlineContext(args.state.agentConfig.reviewer)
64
+ ? await buildPlanReviewerInlineContext({
65
+ state: args.state,
66
+ context,
67
+ reviewedPlanPath: preparedReview.reviewedPlanPath,
68
+ reviewMarkdownPath: args.reviewMarkdownPath,
69
+ })
70
+ : null;
71
+ const reviewedPlanContent = await readTextForInlineSection(resolve(args.state.cwd, preparedReview.reviewedPlanPath));
72
+ const parentPlanContent = context.reviewMode === 'derived-plan' && context.parentPlanDoc
73
+ ? await readTextForInlineSection(resolve(args.state.cwd, context.parentPlanDoc))
74
+ : null;
75
+ const reviewerResult = await (args.runReviewerRound ?? runPlanReviewerRound)({
76
+ reviewer: args.state.agentConfig.reviewer,
77
+ cwd: args.state.cwd,
78
+ planDoc: preparedReview.reviewedPlanPath,
79
+ round: args.round,
80
+ reviewMarkdownPath: args.reviewMarkdownPath,
81
+ mode: context.reviewMode,
82
+ parentPlanDoc: context.parentPlanDoc,
83
+ derivedFromScopeNumber: context.derivedFromScopeNumber,
84
+ reviewerContext: await buildAndPersistReviewerContextPacket({ state: args.state }),
85
+ reviewedPlanContent,
86
+ parentPlanContent,
87
+ inlineContext,
88
+ unattended: args.state.unattended,
89
+ // Reinforce an author-declared `one_shot` only for the top-level authored plan; derived
90
+ // plans declare and own their own shape and are never clamped.
91
+ authoredOneShot: !context.derivedPlanReview && args.state.authoredExecutionShape === 'one_shot',
92
+ logger: args.logger,
93
+ });
94
+ const synthesizedReview = await args.synthesizePlanReviewFindings({
95
+ planPath: context.reviewTargetPath,
96
+ round: args.round,
97
+ roundSummary: reviewerResult.summary,
98
+ findings: reviewerResult.findings.map((finding) => ({
99
+ ...finding,
100
+ source: finding.source,
101
+ })),
102
+ preparedReview,
103
+ });
104
+ return {
105
+ context,
106
+ preparedReview,
107
+ reviewerResult,
108
+ synthesizedReview,
109
+ };
110
+ }
111
+ export async function runPlanningResponseAdjudication(args) {
112
+ const context = resolvePlanningAdjudicationContext(args.state);
113
+ // Top-level `neal plan` refinement (reviewMode === 'plan') is normally authored by a persisted
114
+ // planner session, so a missing handle is a genuine invariant violation for resume-capable
115
+ // planner providers and must keep throwing. Two paths legitimately run without one:
116
+ // derived-plan revisions (reviewMode === 'derived-plan') are authored by the coder during
117
+ // coder_scope and never create a planner session, and no-resume planner providers
118
+ // (`supportsSessionResume: false`) never persist a handle at all. Both start a fresh planner
119
+ // session (resumeHandle: null).
120
+ if (context.reviewMode === 'plan' &&
121
+ !args.state.plannerSessionHandle &&
122
+ !plannerProviderStartsFreshSessions(args.state.agentConfig.planner)) {
123
+ throw new Error('Cannot run planning response adjudication without an existing planner session.');
124
+ }
125
+ const planReviewGuidance = args.state.topLevelMode === 'plan' && context.reviewMode === 'plan'
126
+ ? (args.state.pendingPlanReviewGuidance ?? undefined)
127
+ : undefined;
128
+ const response = await (args.runResponseRound ?? runCoderPlanResponseRound)({
129
+ coder: args.state.agentConfig.planner,
130
+ cwd: args.state.cwd,
131
+ planDoc: context.reviewTargetPath,
132
+ openFindings: args.openFindings,
133
+ mode: args.mode,
134
+ sessionHandle: args.state.plannerSessionHandle ?? null,
135
+ reviewMode: context.reviewMode,
136
+ parentPlanDoc: context.parentPlanDoc,
137
+ derivedFromScopeNumber: context.derivedFromScopeNumber,
138
+ planReviewGuidance,
139
+ logger: args.logger,
140
+ });
141
+ return {
142
+ context,
143
+ response,
144
+ };
145
+ }