@simonren/quorum 0.7.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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -0
  3. package/commands/multi-consult.md +109 -0
  4. package/commands/multi-review.md +139 -0
  5. package/dist/adapters/base.d.ts +120 -0
  6. package/dist/adapters/base.js +98 -0
  7. package/dist/adapters/claude.d.ts +25 -0
  8. package/dist/adapters/claude.js +217 -0
  9. package/dist/adapters/codex.d.ts +20 -0
  10. package/dist/adapters/codex.js +227 -0
  11. package/dist/adapters/gemini.d.ts +20 -0
  12. package/dist/adapters/gemini.js +197 -0
  13. package/dist/adapters/index.d.ts +12 -0
  14. package/dist/adapters/index.js +15 -0
  15. package/dist/cli/check.d.ts +20 -0
  16. package/dist/cli/check.js +78 -0
  17. package/dist/cli/codex.d.ts +11 -0
  18. package/dist/cli/codex.js +255 -0
  19. package/dist/cli/gemini.d.ts +12 -0
  20. package/dist/cli/gemini.js +253 -0
  21. package/dist/commands.d.ts +28 -0
  22. package/dist/commands.js +105 -0
  23. package/dist/config.d.ts +244 -0
  24. package/dist/config.js +179 -0
  25. package/dist/consult-prompt.d.ts +10 -0
  26. package/dist/consult-prompt.js +72 -0
  27. package/dist/context.d.ts +1538 -0
  28. package/dist/context.js +383 -0
  29. package/dist/decoders/claude.d.ts +53 -0
  30. package/dist/decoders/claude.js +106 -0
  31. package/dist/decoders/codex.d.ts +71 -0
  32. package/dist/decoders/codex.js +145 -0
  33. package/dist/decoders/gemini.d.ts +33 -0
  34. package/dist/decoders/gemini.js +58 -0
  35. package/dist/decoders/index.d.ts +6 -0
  36. package/dist/decoders/index.js +3 -0
  37. package/dist/errors.d.ts +46 -0
  38. package/dist/errors.js +192 -0
  39. package/dist/executor.d.ts +103 -0
  40. package/dist/executor.js +244 -0
  41. package/dist/handoff.d.ts +270 -0
  42. package/dist/handoff.js +599 -0
  43. package/dist/index.d.ts +18 -0
  44. package/dist/index.js +134 -0
  45. package/dist/pipeline.d.ts +135 -0
  46. package/dist/pipeline.js +462 -0
  47. package/dist/prompt-v2.d.ts +38 -0
  48. package/dist/prompt-v2.js +391 -0
  49. package/dist/prompt.d.ts +71 -0
  50. package/dist/prompt.js +309 -0
  51. package/dist/schema.d.ts +660 -0
  52. package/dist/schema.js +536 -0
  53. package/dist/tools/consult.d.ts +104 -0
  54. package/dist/tools/consult.js +220 -0
  55. package/dist/tools/feedback.d.ts +91 -0
  56. package/dist/tools/feedback.js +117 -0
  57. package/dist/types.d.ts +105 -0
  58. package/dist/types.js +31 -0
  59. package/package.json +54 -0
@@ -0,0 +1,244 @@
1
+ /**
2
+ * CliExecutor — Shared process management for external AI CLI tools.
3
+ *
4
+ * Extracted from the duplicated spawn logic in codex.ts and gemini.ts to
5
+ * provide a single, well-tested implementation of:
6
+ * - Child-process spawning with stdin delivery
7
+ * - Line-buffered stdout parsing (JSONL-friendly)
8
+ * - Inactivity and absolute-max timeouts
9
+ * - Max buffer size enforcement with truncation flag
10
+ * - Settled guard to prevent double resolve/reject
11
+ * - Dynamic inactivity timeout adjustment via setInactivityTimeout()
12
+ */
13
+ import { spawn } from 'child_process';
14
+ // =============================================================================
15
+ // EXECUTOR
16
+ // =============================================================================
17
+ export class CliExecutor {
18
+ opts;
19
+ /**
20
+ * Mutable inactivity timeout — callers can tighten it after first streaming
21
+ * event via setInactivityTimeout(). Changing it clears and restarts the
22
+ * currently running inactivity timer immediately.
23
+ */
24
+ currentInactivityMs;
25
+ /**
26
+ * Handle to the live inactivity timer (kept here so setInactivityTimeout
27
+ * can reset it from outside the Promise closure via the shared ref below).
28
+ */
29
+ inactivityTimer;
30
+ /**
31
+ * Injected by run() so setInactivityTimeout can restart the timer using
32
+ * the correct reject handle while the process is still running.
33
+ */
34
+ resetInactivityFn;
35
+ constructor(options) {
36
+ this.opts = {
37
+ command: options.command,
38
+ args: options.args,
39
+ cwd: options.cwd,
40
+ stdin: options.stdin,
41
+ env: options.env,
42
+ onLine: options.onLine,
43
+ onStderr: options.onStderr,
44
+ inactivityTimeoutMs: options.inactivityTimeoutMs ?? 120_000,
45
+ maxTimeoutMs: options.maxTimeoutMs ?? 3_600_000,
46
+ maxBufferSize: options.maxBufferSize ?? 1_048_576,
47
+ };
48
+ this.currentInactivityMs = this.opts.inactivityTimeoutMs;
49
+ }
50
+ /**
51
+ * Dynamically adjust the inactivity timeout.
52
+ *
53
+ * If the process is currently running, the active inactivity timer is
54
+ * cancelled and restarted with the new duration immediately. Subsequent
55
+ * activity resets will also use this new value.
56
+ *
57
+ * Typical use: tighten the timeout once the first streaming event arrives,
58
+ * so a stalled process is detected sooner after it starts responding.
59
+ */
60
+ setInactivityTimeout(ms) {
61
+ this.currentInactivityMs = ms;
62
+ // Restart the running timer (if any) with the new duration.
63
+ this.resetInactivityFn?.();
64
+ }
65
+ /**
66
+ * Spawn the process and return a promise that resolves with CliResult on
67
+ * normal completion (any exit code) or rejects with:
68
+ * - Error('TIMEOUT') — inactivity timeout exceeded
69
+ * - Error('MAX_TIMEOUT') — absolute max timeout exceeded
70
+ * - ENOENT / other spawn errors propagated from child_process
71
+ */
72
+ run() {
73
+ return new Promise((resolve, reject) => {
74
+ // ------------------------------------------------------------------
75
+ // Settled guard — prevents double resolve/reject when, e.g., the
76
+ // inactivity timer fires and then the `close` event also fires.
77
+ // ------------------------------------------------------------------
78
+ let settled = false;
79
+ const settle = (fn) => {
80
+ if (settled)
81
+ return;
82
+ settled = true;
83
+ fn();
84
+ };
85
+ // ------------------------------------------------------------------
86
+ // Spawn
87
+ // ------------------------------------------------------------------
88
+ const proc = spawn(this.opts.command, this.opts.args, {
89
+ cwd: this.opts.cwd,
90
+ stdio: ['pipe', 'pipe', 'pipe'],
91
+ env: this.opts.env
92
+ ? { ...process.env, ...this.opts.env }
93
+ : { ...process.env },
94
+ });
95
+ // ------------------------------------------------------------------
96
+ // Stdin delivery
97
+ // ------------------------------------------------------------------
98
+ // Guard against EPIPE: log but do not reject — the close handler owns
99
+ // the final resolution. EPIPE means the child exited before consuming
100
+ // all of stdin, which is fine (e.g. the child crashed early).
101
+ proc.stdin.on('error', (err) => {
102
+ if (err.code === 'EPIPE') {
103
+ console.error(`[executor] stdin EPIPE (child closed early): ${err.message}`);
104
+ }
105
+ else {
106
+ console.error(`[executor] stdin error: ${err.message}`);
107
+ }
108
+ });
109
+ if (this.opts.stdin !== undefined) {
110
+ proc.stdin.write(this.opts.stdin);
111
+ }
112
+ proc.stdin.end();
113
+ // ------------------------------------------------------------------
114
+ // State
115
+ // ------------------------------------------------------------------
116
+ let rawStdout = '';
117
+ let stderr = '';
118
+ let truncated = false;
119
+ /** Partial line buffer — accumulates data until a '\n' is seen. */
120
+ let lineBuffer = '';
121
+ // ------------------------------------------------------------------
122
+ // Timers
123
+ // ------------------------------------------------------------------
124
+ const maxTimer = setTimeout(() => {
125
+ proc.kill('SIGTERM');
126
+ settle(() => reject(new Error('MAX_TIMEOUT')));
127
+ }, this.opts.maxTimeoutMs);
128
+ const resetInactivity = () => {
129
+ clearTimeout(this.inactivityTimer);
130
+ this.inactivityTimer = setTimeout(() => {
131
+ proc.kill('SIGTERM');
132
+ settle(() => reject(new Error('TIMEOUT')));
133
+ }, this.currentInactivityMs);
134
+ };
135
+ // Expose reset function so setInactivityTimeout() can restart it.
136
+ this.resetInactivityFn = resetInactivity;
137
+ // Start the inactivity clock.
138
+ resetInactivity();
139
+ // ------------------------------------------------------------------
140
+ // stdout handler — line buffering
141
+ // ------------------------------------------------------------------
142
+ proc.stdout.on('data', (chunk) => {
143
+ resetInactivity();
144
+ const incoming = chunk.toString();
145
+ // Buffer management: cap rawStdout at maxBufferSize.
146
+ if (!truncated) {
147
+ const remaining = this.opts.maxBufferSize - rawStdout.length;
148
+ if (incoming.length <= remaining) {
149
+ rawStdout += incoming;
150
+ }
151
+ else {
152
+ rawStdout += incoming.slice(0, remaining);
153
+ truncated = true;
154
+ }
155
+ }
156
+ // Line splitting — process complete lines from the combined buffer.
157
+ lineBuffer += incoming;
158
+ const newlineIdx = lineBuffer.lastIndexOf('\n');
159
+ if (newlineIdx === -1) {
160
+ // No complete line yet — keep buffering.
161
+ return;
162
+ }
163
+ // Extract all complete lines (everything up to and including the last '\n').
164
+ const completePart = lineBuffer.slice(0, newlineIdx + 1);
165
+ lineBuffer = lineBuffer.slice(newlineIdx + 1);
166
+ const lines = completePart.split('\n');
167
+ // The last element is always '' because split on a trailing '\n' produces
168
+ // an empty string — skip it.
169
+ for (let i = 0; i < lines.length - 1; i++) {
170
+ const line = lines[i];
171
+ if (this.opts.onLine) {
172
+ try {
173
+ this.opts.onLine(line);
174
+ }
175
+ catch {
176
+ // Intentionally swallowed — callers must not break the executor.
177
+ }
178
+ }
179
+ }
180
+ });
181
+ // ------------------------------------------------------------------
182
+ // stderr handler
183
+ // ------------------------------------------------------------------
184
+ proc.stderr.on('data', (chunk) => {
185
+ resetInactivity();
186
+ const data = chunk.toString();
187
+ if (stderr.length < this.opts.maxBufferSize) {
188
+ const remaining = this.opts.maxBufferSize - stderr.length;
189
+ stderr += data.length <= remaining ? data : data.slice(0, remaining);
190
+ }
191
+ if (this.opts.onStderr) {
192
+ try {
193
+ this.opts.onStderr(data);
194
+ }
195
+ catch {
196
+ // Intentionally swallowed.
197
+ }
198
+ }
199
+ });
200
+ // ------------------------------------------------------------------
201
+ // close handler — normal resolution path
202
+ // ------------------------------------------------------------------
203
+ proc.on('close', (code) => {
204
+ clearTimeout(this.inactivityTimer);
205
+ clearTimeout(maxTimer);
206
+ this.resetInactivityFn = undefined;
207
+ // Flush any partial line still in the buffer.
208
+ if (lineBuffer.length > 0) {
209
+ if (this.opts.onLine) {
210
+ try {
211
+ this.opts.onLine(lineBuffer);
212
+ }
213
+ catch {
214
+ // Intentionally swallowed.
215
+ }
216
+ }
217
+ }
218
+ // Build the final lines array from rawStdout (split after the fact so
219
+ // the list is consistent with rawStdout even when truncated).
220
+ const rawLines = rawStdout.split('\n');
221
+ // Drop a trailing empty string produced by a final '\n'.
222
+ if (rawLines.length > 0 && rawLines[rawLines.length - 1] === '') {
223
+ rawLines.pop();
224
+ }
225
+ settle(() => resolve({
226
+ stdoutLines: rawLines,
227
+ rawStdout,
228
+ stderr,
229
+ exitCode: code ?? -1,
230
+ truncated,
231
+ }));
232
+ });
233
+ // ------------------------------------------------------------------
234
+ // error handler — spawn failures (e.g. ENOENT)
235
+ // ------------------------------------------------------------------
236
+ proc.on('error', (err) => {
237
+ clearTimeout(this.inactivityTimer);
238
+ clearTimeout(maxTimer);
239
+ this.resetInactivityFn = undefined;
240
+ settle(() => reject(err));
241
+ });
242
+ });
243
+ }
244
+ }
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Review Handoff Protocol
3
+ *
4
+ * Defines the minimal, targeted information that should flow from CC to reviewers.
5
+ *
6
+ * Philosophy:
7
+ * - Reviewers have filesystem access - don't duplicate what they can discover
8
+ * - Pass ONLY what CC uniquely knows: uncertainties, decisions, questions
9
+ * - Let reviewer use their tools (file reading) for actual code
10
+ * - Do NOT assume git — working directory may not be a git repo
11
+ */
12
+ import { z } from 'zod';
13
+ import { FocusArea } from './types.js';
14
+ export { FocusArea } from './types.js';
15
+ /**
16
+ * Uncertainty that CC has - things the reviewer should verify
17
+ */
18
+ export declare const UncertaintySchema: z.ZodObject<{
19
+ topic: z.ZodString;
20
+ question: z.ZodString;
21
+ ccAssumption: z.ZodOptional<z.ZodString>;
22
+ relevantFiles: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
23
+ severity: z.ZodOptional<z.ZodEnum<["critical", "important", "minor"]>>;
24
+ }, "strip", z.ZodTypeAny, {
25
+ question: string;
26
+ topic: string;
27
+ relevantFiles?: string[] | undefined;
28
+ severity?: "critical" | "important" | "minor" | undefined;
29
+ ccAssumption?: string | undefined;
30
+ }, {
31
+ question: string;
32
+ topic: string;
33
+ relevantFiles?: string[] | undefined;
34
+ severity?: "critical" | "important" | "minor" | undefined;
35
+ ccAssumption?: string | undefined;
36
+ }>;
37
+ export type Uncertainty = z.infer<typeof UncertaintySchema>;
38
+ /**
39
+ * Decision CC made - for reviewer to evaluate
40
+ */
41
+ export declare const DecisionSchema: z.ZodObject<{
42
+ decision: z.ZodString;
43
+ rationale: z.ZodString;
44
+ alternatives: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
45
+ tradeoffs: z.ZodOptional<z.ZodString>;
46
+ }, "strip", z.ZodTypeAny, {
47
+ decision: string;
48
+ rationale: string;
49
+ alternatives?: string[] | undefined;
50
+ tradeoffs?: string | undefined;
51
+ }, {
52
+ decision: string;
53
+ rationale: string;
54
+ alternatives?: string[] | undefined;
55
+ tradeoffs?: string | undefined;
56
+ }>;
57
+ export type Decision = z.infer<typeof DecisionSchema>;
58
+ /**
59
+ * Question CC wants the reviewer to answer
60
+ */
61
+ export declare const QuestionSchema: z.ZodObject<{
62
+ question: z.ZodString;
63
+ context: z.ZodOptional<z.ZodString>;
64
+ ccGuess: z.ZodOptional<z.ZodString>;
65
+ }, "strip", z.ZodTypeAny, {
66
+ question: string;
67
+ context?: string | undefined;
68
+ ccGuess?: string | undefined;
69
+ }, {
70
+ question: string;
71
+ context?: string | undefined;
72
+ ccGuess?: string | undefined;
73
+ }>;
74
+ export type Question = z.infer<typeof QuestionSchema>;
75
+ /**
76
+ * The complete handoff from CC to reviewer
77
+ * Intentionally minimal - only what CC uniquely knows
78
+ */
79
+ export declare const HandoffSchema: z.ZodObject<{
80
+ workingDir: z.ZodString;
81
+ summary: z.ZodString;
82
+ uncertainties: z.ZodOptional<z.ZodArray<z.ZodObject<{
83
+ topic: z.ZodString;
84
+ question: z.ZodString;
85
+ ccAssumption: z.ZodOptional<z.ZodString>;
86
+ relevantFiles: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
87
+ severity: z.ZodOptional<z.ZodEnum<["critical", "important", "minor"]>>;
88
+ }, "strip", z.ZodTypeAny, {
89
+ question: string;
90
+ topic: string;
91
+ relevantFiles?: string[] | undefined;
92
+ severity?: "critical" | "important" | "minor" | undefined;
93
+ ccAssumption?: string | undefined;
94
+ }, {
95
+ question: string;
96
+ topic: string;
97
+ relevantFiles?: string[] | undefined;
98
+ severity?: "critical" | "important" | "minor" | undefined;
99
+ ccAssumption?: string | undefined;
100
+ }>, "many">>;
101
+ decisions: z.ZodOptional<z.ZodArray<z.ZodObject<{
102
+ decision: z.ZodString;
103
+ rationale: z.ZodString;
104
+ alternatives: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
105
+ tradeoffs: z.ZodOptional<z.ZodString>;
106
+ }, "strip", z.ZodTypeAny, {
107
+ decision: string;
108
+ rationale: string;
109
+ alternatives?: string[] | undefined;
110
+ tradeoffs?: string | undefined;
111
+ }, {
112
+ decision: string;
113
+ rationale: string;
114
+ alternatives?: string[] | undefined;
115
+ tradeoffs?: string | undefined;
116
+ }>, "many">>;
117
+ questions: z.ZodOptional<z.ZodArray<z.ZodObject<{
118
+ question: z.ZodString;
119
+ context: z.ZodOptional<z.ZodString>;
120
+ ccGuess: z.ZodOptional<z.ZodString>;
121
+ }, "strip", z.ZodTypeAny, {
122
+ question: string;
123
+ context?: string | undefined;
124
+ ccGuess?: string | undefined;
125
+ }, {
126
+ question: string;
127
+ context?: string | undefined;
128
+ ccGuess?: string | undefined;
129
+ }>, "many">>;
130
+ priorityFiles: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
131
+ focusAreas: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
132
+ confidence: z.ZodOptional<z.ZodNumber>;
133
+ customInstructions: z.ZodOptional<z.ZodString>;
134
+ }, "strip", z.ZodTypeAny, {
135
+ workingDir: string;
136
+ summary: string;
137
+ confidence?: number | undefined;
138
+ uncertainties?: {
139
+ question: string;
140
+ topic: string;
141
+ relevantFiles?: string[] | undefined;
142
+ severity?: "critical" | "important" | "minor" | undefined;
143
+ ccAssumption?: string | undefined;
144
+ }[] | undefined;
145
+ decisions?: {
146
+ decision: string;
147
+ rationale: string;
148
+ alternatives?: string[] | undefined;
149
+ tradeoffs?: string | undefined;
150
+ }[] | undefined;
151
+ questions?: {
152
+ question: string;
153
+ context?: string | undefined;
154
+ ccGuess?: string | undefined;
155
+ }[] | undefined;
156
+ focusAreas?: string[] | undefined;
157
+ customInstructions?: string | undefined;
158
+ priorityFiles?: string[] | undefined;
159
+ }, {
160
+ workingDir: string;
161
+ summary: string;
162
+ confidence?: number | undefined;
163
+ uncertainties?: {
164
+ question: string;
165
+ topic: string;
166
+ relevantFiles?: string[] | undefined;
167
+ severity?: "critical" | "important" | "minor" | undefined;
168
+ ccAssumption?: string | undefined;
169
+ }[] | undefined;
170
+ decisions?: {
171
+ decision: string;
172
+ rationale: string;
173
+ alternatives?: string[] | undefined;
174
+ tradeoffs?: string | undefined;
175
+ }[] | undefined;
176
+ questions?: {
177
+ question: string;
178
+ context?: string | undefined;
179
+ ccGuess?: string | undefined;
180
+ }[] | undefined;
181
+ focusAreas?: string[] | undefined;
182
+ customInstructions?: string | undefined;
183
+ priorityFiles?: string[] | undefined;
184
+ }>;
185
+ export type Handoff = z.infer<typeof HandoffSchema>;
186
+ export interface ReviewerRole {
187
+ id: string;
188
+ name: string;
189
+ description: string;
190
+ isGeneric: boolean;
191
+ applicableFocusAreas: FocusArea[];
192
+ systemPrompt: string;
193
+ }
194
+ /**
195
+ * Strong generic role - when no specific focus is given
196
+ * This is NOT a weak fallback - it's a comprehensive reviewer
197
+ */
198
+ export declare const COMPREHENSIVE_REVIEWER: ReviewerRole;
199
+ /**
200
+ * Change-focused reviewer - specifically for reviewing diffs
201
+ */
202
+ export declare const CHANGE_FOCUSED_REVIEWER: ReviewerRole;
203
+ /**
204
+ * Specialized roles - when specific focus is requested
205
+ */
206
+ export declare const SECURITY_REVIEWER: ReviewerRole;
207
+ export declare const PERFORMANCE_REVIEWER: ReviewerRole;
208
+ export declare const ARCHITECTURE_REVIEWER: ReviewerRole;
209
+ export declare const CORRECTNESS_REVIEWER: ReviewerRole;
210
+ export declare const ROLES: Record<string, ReviewerRole>;
211
+ /**
212
+ * Select and compose roles based on focus areas.
213
+ *
214
+ * When multiple focus areas map to different roles (e.g. security + performance),
215
+ * composes them into a single role with merged prompts instead of picking one winner.
216
+ */
217
+ export declare function selectRole(focusAreas?: FocusArea[]): ReviewerRole;
218
+ export declare const ADVERSARIAL_REVIEWER: ReviewerRole;
219
+ /**
220
+ * Build an adversarial handoff prompt with challenge-mode stance sections.
221
+ *
222
+ * Block structure ported from openai/codex-plugin-cc's adversarial-review
223
+ * prompt: tagged XML blocks (operating_stance, attack_surface, review_method,
224
+ * finding_bar, calibration_rules, grounding_rules, final_check) so the prompt
225
+ * has stable internal structure the reviewer can lean on. CC's handoff
226
+ * sections (uncertainties / decisions / questions / focus / files / focus
227
+ * instructions) are layered on after as our differentiator.
228
+ */
229
+ export declare function buildAdversarialHandoffPrompt(options: PromptOptions): string;
230
+ export interface PromptOptions {
231
+ handoff: Handoff;
232
+ role?: ReviewerRole;
233
+ }
234
+ /**
235
+ * Build the review prompt using minimal, targeted context.
236
+ * No output format constraints — reviewer responds naturally, CC interprets.
237
+ */
238
+ export declare function buildHandoffPrompt(options: PromptOptions): string;
239
+ /**
240
+ * Parse structured ccOutput into Handoff fields.
241
+ *
242
+ * The slash commands tell CC to format its output as:
243
+ * SUMMARY:
244
+ * <text>
245
+ *
246
+ * UNCERTAINTIES (verify these):
247
+ * 1. <text>
248
+ *
249
+ * QUESTIONS:
250
+ * 1. <text>
251
+ *
252
+ * PRIORITY FILES:
253
+ * - <file>
254
+ *
255
+ * If no sections detected, returns { summary: ccOutput } (graceful fallback).
256
+ */
257
+ export declare function parseStructuredCcOutput(ccOutput: string): Pick<Handoff, 'summary'> & Partial<Handoff>;
258
+ /**
259
+ * Build a handoff from MCP tool inputs.
260
+ *
261
+ * Parses structured sections (SUMMARY, UNCERTAINTIES, QUESTIONS, PRIORITY FILES)
262
+ * from ccOutput when present, populating typed Handoff fields so reviewers
263
+ * receive machine-usable context instead of a single summary blob.
264
+ */
265
+ export declare function buildSimpleHandoff(workingDir: string, ccOutput: string, analyzedFiles?: string[], focusAreas?: string[], customPrompt?: string): Handoff;
266
+ /**
267
+ * Enhance a simple handoff with uncertainties/questions
268
+ * CC should call this to add its specific concerns
269
+ */
270
+ export declare function enhanceHandoff(handoff: Handoff, uncertainties?: Uncertainty[], questions?: Question[], decisions?: Decision[]): Handoff;