@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.
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/commands/multi-consult.md +109 -0
- package/commands/multi-review.md +139 -0
- package/dist/adapters/base.d.ts +120 -0
- package/dist/adapters/base.js +98 -0
- package/dist/adapters/claude.d.ts +25 -0
- package/dist/adapters/claude.js +217 -0
- package/dist/adapters/codex.d.ts +20 -0
- package/dist/adapters/codex.js +227 -0
- package/dist/adapters/gemini.d.ts +20 -0
- package/dist/adapters/gemini.js +197 -0
- package/dist/adapters/index.d.ts +12 -0
- package/dist/adapters/index.js +15 -0
- package/dist/cli/check.d.ts +20 -0
- package/dist/cli/check.js +78 -0
- package/dist/cli/codex.d.ts +11 -0
- package/dist/cli/codex.js +255 -0
- package/dist/cli/gemini.d.ts +12 -0
- package/dist/cli/gemini.js +253 -0
- package/dist/commands.d.ts +28 -0
- package/dist/commands.js +105 -0
- package/dist/config.d.ts +244 -0
- package/dist/config.js +179 -0
- package/dist/consult-prompt.d.ts +10 -0
- package/dist/consult-prompt.js +72 -0
- package/dist/context.d.ts +1538 -0
- package/dist/context.js +383 -0
- package/dist/decoders/claude.d.ts +53 -0
- package/dist/decoders/claude.js +106 -0
- package/dist/decoders/codex.d.ts +71 -0
- package/dist/decoders/codex.js +145 -0
- package/dist/decoders/gemini.d.ts +33 -0
- package/dist/decoders/gemini.js +58 -0
- package/dist/decoders/index.d.ts +6 -0
- package/dist/decoders/index.js +3 -0
- package/dist/errors.d.ts +46 -0
- package/dist/errors.js +192 -0
- package/dist/executor.d.ts +103 -0
- package/dist/executor.js +244 -0
- package/dist/handoff.d.ts +270 -0
- package/dist/handoff.js +599 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +134 -0
- package/dist/pipeline.d.ts +135 -0
- package/dist/pipeline.js +462 -0
- package/dist/prompt-v2.d.ts +38 -0
- package/dist/prompt-v2.js +391 -0
- package/dist/prompt.d.ts +71 -0
- package/dist/prompt.js +309 -0
- package/dist/schema.d.ts +660 -0
- package/dist/schema.js +536 -0
- package/dist/tools/consult.d.ts +104 -0
- package/dist/tools/consult.js +220 -0
- package/dist/tools/feedback.d.ts +91 -0
- package/dist/tools/feedback.js +117 -0
- package/dist/types.d.ts +105 -0
- package/dist/types.js +31 -0
- package/package.json +54 -0
package/dist/executor.js
ADDED
|
@@ -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;
|