@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,599 @@
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
+ // =============================================================================
14
+ // HANDOFF SCHEMA - What CC Passes to Reviewer
15
+ // =============================================================================
16
+ /**
17
+ * Uncertainty that CC has - things the reviewer should verify
18
+ */
19
+ export const UncertaintySchema = z.object({
20
+ topic: z.string().describe('What CC is uncertain about'),
21
+ question: z.string().describe('The specific question'),
22
+ ccAssumption: z.string().optional().describe("What CC assumed/did - reviewer should verify"),
23
+ relevantFiles: z.array(z.string()).optional().describe('Files related to this uncertainty'),
24
+ severity: z.enum(['critical', 'important', 'minor']).optional(),
25
+ });
26
+ /**
27
+ * Decision CC made - for reviewer to evaluate
28
+ */
29
+ export const DecisionSchema = z.object({
30
+ decision: z.string().describe('What CC decided'),
31
+ rationale: z.string().describe('Why CC chose this'),
32
+ alternatives: z.array(z.string()).optional().describe('Other options considered'),
33
+ tradeoffs: z.string().optional().describe('Known tradeoffs of this choice'),
34
+ });
35
+ /**
36
+ * Question CC wants the reviewer to answer
37
+ */
38
+ export const QuestionSchema = z.object({
39
+ question: z.string(),
40
+ context: z.string().optional(),
41
+ ccGuess: z.string().optional().describe("CC's best guess - for comparison"),
42
+ });
43
+ /**
44
+ * The complete handoff from CC to reviewer
45
+ * Intentionally minimal - only what CC uniquely knows
46
+ */
47
+ export const HandoffSchema = z.object({
48
+ // Working directory (required for filesystem access)
49
+ workingDir: z.string(),
50
+ // Brief summary of what CC did (1-3 sentences)
51
+ summary: z.string().describe('Brief: what CC did and why'),
52
+ // CC's uncertainties - things reviewer should verify
53
+ uncertainties: z.array(UncertaintySchema).optional(),
54
+ // Key decisions CC made - for reviewer to evaluate
55
+ decisions: z.array(DecisionSchema).optional(),
56
+ // Specific questions CC wants answered
57
+ questions: z.array(QuestionSchema).optional(),
58
+ // Files to prioritize (if CC knows which are most important)
59
+ priorityFiles: z.array(z.string()).optional(),
60
+ // Focus areas (security, performance, etc.)
61
+ focusAreas: z.array(z.string()).optional(),
62
+ // Overall confidence (0-1)
63
+ confidence: z.number().min(0).max(1).optional(),
64
+ // Custom instructions from user
65
+ customInstructions: z.string().optional(),
66
+ });
67
+ /**
68
+ * Strong generic role - when no specific focus is given
69
+ * This is NOT a weak fallback - it's a comprehensive reviewer
70
+ */
71
+ export const COMPREHENSIVE_REVIEWER = {
72
+ id: 'comprehensive',
73
+ name: 'Comprehensive Code Reviewer',
74
+ description: 'Systematic review across all dimensions, prioritizing high-impact issues',
75
+ isGeneric: true,
76
+ applicableFocusAreas: [],
77
+ systemPrompt: `Senior staff engineer. Be skeptical — catch mistakes, don't rubber-stamp.
78
+ Priority: correctness > security > performance > maintainability.
79
+ Only report real issues with evidence.`,
80
+ };
81
+ /**
82
+ * Change-focused reviewer - specifically for reviewing diffs
83
+ */
84
+ export const CHANGE_FOCUSED_REVIEWER = {
85
+ id: 'change_focused',
86
+ name: 'Change Reviewer',
87
+ description: 'Focused on reviewing the delta - what changed and its implications',
88
+ isGeneric: true,
89
+ applicableFocusAreas: [],
90
+ systemPrompt: `Change reviewer. Focus on: goal achievement, regressions, edge cases, side effects.
91
+ Reference specific lines in the source files.`,
92
+ };
93
+ /**
94
+ * Specialized roles - when specific focus is requested
95
+ */
96
+ export const SECURITY_REVIEWER = {
97
+ id: 'security',
98
+ name: 'Security Auditor',
99
+ description: 'Deep security analysis with OWASP/CWE focus',
100
+ isGeneric: false,
101
+ applicableFocusAreas: ['security'],
102
+ systemPrompt: `Security auditor. Focus on injection, auth bypass, data exposure, input validation.
103
+ Rate by exploitability + impact.`,
104
+ };
105
+ export const PERFORMANCE_REVIEWER = {
106
+ id: 'performance',
107
+ name: 'Performance Engineer',
108
+ description: 'Performance and efficiency analysis',
109
+ isGeneric: false,
110
+ applicableFocusAreas: ['performance', 'scalability'],
111
+ systemPrompt: `Performance engineer. Focus on complexity (Big-O), N+1 queries, memory, blocking I/O.
112
+ Provide complexity analysis and specific optimizations.`,
113
+ };
114
+ export const ARCHITECTURE_REVIEWER = {
115
+ id: 'architecture',
116
+ name: 'Software Architect',
117
+ description: 'Design patterns, structure, and maintainability',
118
+ isGeneric: false,
119
+ applicableFocusAreas: ['architecture', 'maintainability'],
120
+ systemPrompt: `Software architect. Focus on SOLID, coupling/cohesion, abstractions, patterns.
121
+ Suggest refactorings with specific patterns.`,
122
+ };
123
+ export const CORRECTNESS_REVIEWER = {
124
+ id: 'correctness',
125
+ name: 'Correctness Analyst',
126
+ description: 'Logic errors, edge cases, and bug detection',
127
+ isGeneric: false,
128
+ applicableFocusAreas: ['correctness', 'testing'],
129
+ systemPrompt: `Correctness analyst. Focus on logic errors, edge cases, race conditions, error handling.
130
+ Provide triggering inputs and expected vs actual behavior.
131
+ For significant bugs, suggest a concrete regression test (name, inputs, expected output).`,
132
+ };
133
+ // All roles indexed by ID
134
+ export const ROLES = {
135
+ comprehensive: COMPREHENSIVE_REVIEWER,
136
+ change_focused: CHANGE_FOCUSED_REVIEWER,
137
+ security: SECURITY_REVIEWER,
138
+ performance: PERFORMANCE_REVIEWER,
139
+ architecture: ARCHITECTURE_REVIEWER,
140
+ correctness: CORRECTNESS_REVIEWER,
141
+ };
142
+ /**
143
+ * Select and compose roles based on focus areas.
144
+ *
145
+ * When multiple focus areas map to different roles (e.g. security + performance),
146
+ * composes them into a single role with merged prompts instead of picking one winner.
147
+ */
148
+ export function selectRole(focusAreas) {
149
+ if (!focusAreas || focusAreas.length === 0) {
150
+ return COMPREHENSIVE_REVIEWER;
151
+ }
152
+ // Collect all unique matching roles (preserving insertion order)
153
+ const matched = new Map();
154
+ for (const focus of focusAreas) {
155
+ for (const role of Object.values(ROLES)) {
156
+ if (!role.isGeneric && role.applicableFocusAreas.includes(focus)) {
157
+ matched.set(role.id, role);
158
+ }
159
+ }
160
+ }
161
+ if (matched.size === 0)
162
+ return CHANGE_FOCUSED_REVIEWER;
163
+ if (matched.size === 1)
164
+ return [...matched.values()][0];
165
+ // Compose multiple roles into one
166
+ const roles = [...matched.values()];
167
+ return {
168
+ id: roles.map(r => r.id).join('+'),
169
+ name: roles.map(r => r.name).join(' + '),
170
+ description: roles.map(r => r.description).join('; '),
171
+ isGeneric: false,
172
+ applicableFocusAreas: focusAreas,
173
+ systemPrompt: roles.map(r => `**As ${r.name}:** ${r.systemPrompt}`).join('\n'),
174
+ };
175
+ }
176
+ // =============================================================================
177
+ // ADVERSARIAL REVIEWER — Challenge mode for multi_review
178
+ // =============================================================================
179
+ export const ADVERSARIAL_REVIEWER = {
180
+ id: 'adversarial',
181
+ name: 'Adversarial Reviewer',
182
+ description: 'Actively tries to break confidence in the change — challenges assumptions, not just bugs',
183
+ isGeneric: false,
184
+ applicableFocusAreas: [],
185
+ systemPrompt: `Senior staff engineer performing an adversarial review. Your job is to break confidence in the change, not to validate it.`,
186
+ };
187
+ /**
188
+ * Build an adversarial handoff prompt with challenge-mode stance sections.
189
+ *
190
+ * Block structure ported from openai/codex-plugin-cc's adversarial-review
191
+ * prompt: tagged XML blocks (operating_stance, attack_surface, review_method,
192
+ * finding_bar, calibration_rules, grounding_rules, final_check) so the prompt
193
+ * has stable internal structure the reviewer can lean on. CC's handoff
194
+ * sections (uncertainties / decisions / questions / focus / files / focus
195
+ * instructions) are layered on after as our differentiator.
196
+ */
197
+ export function buildAdversarialHandoffPrompt(options) {
198
+ const { handoff } = options;
199
+ const role = ADVERSARIAL_REVIEWER;
200
+ const sections = [];
201
+ // SECTION 1: ROLE
202
+ sections.push(`# ROLE: ${role.name}\n\n${role.systemPrompt}`);
203
+ // SECTION 2: ADVERSARIAL STANCE — tagged blocks form the operating contract
204
+ sections.push(`<operating_stance>
205
+ Default to skepticism.
206
+ Assume the change can fail in subtle, high-cost, or user-visible ways until the evidence says otherwise.
207
+ Do not give credit for good intent, partial fixes, or likely follow-up work.
208
+ If something only works on the happy path, treat that as a real weakness.
209
+ </operating_stance>
210
+
211
+ <attack_surface>
212
+ Prioritize the kinds of failures that are expensive, dangerous, or hard to detect:
213
+ - auth, permissions, tenant isolation, and trust boundaries
214
+ - data loss, corruption, duplication, and irreversible state changes
215
+ - rollback safety, retries, partial failure, and idempotency gaps
216
+ - race conditions, ordering assumptions, stale state, and re-entrancy
217
+ - empty-state, null, timeout, and degraded dependency behavior
218
+ - version skew, schema drift, migration hazards, and compatibility regressions
219
+ - observability gaps that would hide failure or make recovery harder
220
+ </attack_surface>
221
+
222
+ <review_method>
223
+ Actively try to disprove the change.
224
+ Look for violated invariants, missing guards, unhandled failure paths, and assumptions that stop being true under stress.
225
+ Trace how bad inputs, retries, concurrent actions, or partially completed operations move through the code.
226
+ If the user supplied a focus area, weight it heavily, but still report any other material issue you can defend.
227
+ </review_method>
228
+
229
+ <finding_bar>
230
+ Report only material findings.
231
+ Do not include style feedback, naming feedback, low-value cleanup, or speculative concerns without evidence.
232
+ Each finding must answer:
233
+ 1. What can go wrong?
234
+ 2. Why is this code path vulnerable?
235
+ 3. What is the likely impact?
236
+ 4. What concrete change would reduce the risk?
237
+ </finding_bar>
238
+
239
+ <calibration_rules>
240
+ Prefer one strong finding over several weak ones.
241
+ Do not dilute serious issues with filler.
242
+ If the change looks safe, say so directly and return no findings.
243
+ </calibration_rules>
244
+
245
+ <grounding_rules>
246
+ Be aggressive, but stay grounded.
247
+ Every finding must be defensible from the repository context or tool outputs.
248
+ Do not invent files, lines, code paths, incidents, attack chains, or runtime behavior you cannot support.
249
+ If a conclusion depends on an inference, state that explicitly in the finding body and keep the confidence honest.
250
+ </grounding_rules>
251
+
252
+ <final_check>
253
+ Before finalizing, check that each finding is:
254
+ - adversarial rather than stylistic
255
+ - tied to a concrete code location
256
+ - plausible under a real failure scenario
257
+ - actionable for an engineer fixing the issue
258
+ </final_check>`);
259
+ // SECTION 3: TASK (same as standard)
260
+ sections.push(`## YOUR TASK
261
+
262
+ Review code in \`${handoff.workingDir}\`.
263
+
264
+ **Summary:** ${handoff.summary}${handoff.confidence !== undefined && handoff.confidence < 0.9 ? `\n**CC Confidence:** ${Math.round(handoff.confidence * 100)}% — verify weak areas` : ''}
265
+
266
+ **IMPORTANT:**
267
+ - This is a READ-ONLY review. Do NOT create, modify, or delete any files. Only read files to verify claims.
268
+ - Do NOT assume a git repository exists. Do NOT run git commands. Read files directly from the filesystem.`);
269
+ // SECTION 4: CC'S UNCERTAINTIES
270
+ if (handoff.uncertainties && handoff.uncertainties.length > 0) {
271
+ sections.push(`## CC'S UNCERTAINTIES
272
+
273
+ ${handoff.uncertainties.map((u, i) => `### ${i + 1}. ${u.topic} ${u.severity === 'critical' ? '⚠️' : ''}
274
+ - **Question:** ${u.question}
275
+ ${u.ccAssumption ? `- **CC assumed:** ${u.ccAssumption}` : ''}
276
+ ${u.relevantFiles ? `- **Files:** ${u.relevantFiles.join(', ')}` : ''}`).join('\n\n')}`);
277
+ }
278
+ // SECTION 5: SPECIFIC QUESTIONS
279
+ if (handoff.questions && handoff.questions.length > 0) {
280
+ sections.push(`## QUESTIONS FROM CC
281
+
282
+ ${handoff.questions.map((q, i) => `${i + 1}. **${q.question}**
283
+ ${q.context ? `Context: ${q.context}` : ''}
284
+ ${q.ccGuess ? `CC Guess: ${q.ccGuess}` : ''}`).join('\n')}`);
285
+ }
286
+ // SECTION 6: DECISIONS TO EVALUATE
287
+ if (handoff.decisions && handoff.decisions.length > 0) {
288
+ sections.push(`## DECISIONS TO EVALUATE
289
+
290
+ ${handoff.decisions.map((d, i) => `${i + 1}. **${d.decision}**${d.rationale ? `\n Rationale: ${d.rationale}` : ''}${d.alternatives ? `\n Alternatives: ${d.alternatives.join(', ')}` : ''}`).join('\n')}`);
291
+ }
292
+ // SECTION 7: FOCUS AREAS
293
+ if (handoff.focusAreas && handoff.focusAreas.length > 0) {
294
+ sections.push(`## FOCUS AREAS\n\nWeight these areas heavily in your adversarial analysis:\n${handoff.focusAreas.map(f => `- **${f}**`).join('\n')}`);
295
+ }
296
+ // SECTION 8: PRIORITY FILES
297
+ if (handoff.priorityFiles && handoff.priorityFiles.length > 0) {
298
+ sections.push(`## PRIORITY FILES\n\n${handoff.priorityFiles.map(f => `- \`${f}\``).join('\n')}`);
299
+ }
300
+ // SECTION 9: ADVERSARIAL FOCUS (customInstructions steers the challenge)
301
+ if (handoff.customInstructions) {
302
+ sections.push(`## ADVERSARIAL FOCUS\n\n${handoff.customInstructions}`);
303
+ }
304
+ return sections.join('\n\n');
305
+ }
306
+ // =============================================================================
307
+ // FOCUS-AREA CHECKLISTS — Specific patterns to look for (ported from prompt-v2)
308
+ // =============================================================================
309
+ const FOCUS_CHECKLISTS = {
310
+ security: `Check for:
311
+ - Injection vulnerabilities (SQL, NoSQL, Command, XSS)
312
+ - Auth/authorization bypass, session management flaws
313
+ - Sensitive data exposure, insecure storage, missing encryption
314
+ - Input validation gaps (type, range, format)
315
+ - Path traversal, SSRF, unsafe deserialization
316
+ For each: CWE ID if applicable, attack scenario, severity by impact + exploitability.`,
317
+ performance: `Check for:
318
+ - Algorithmic complexity (provide Big-O notation)
319
+ - N+1 queries, missing indexes, unoptimized queries
320
+ - Blocking I/O in async contexts
321
+ - Memory leaks, unbounded allocations, large object retention
322
+ - Missing caching/memoization, repeated expensive operations
323
+ For each: Big-O analysis, estimated impact, concrete optimization.`,
324
+ architecture: `Check for:
325
+ - SOLID violations (SRP, OCP, LSP, ISP, DIP)
326
+ - High coupling between modules, low cohesion within
327
+ - Layering violations, circular dependencies
328
+ - Anti-patterns (god classes, deep nesting, magic numbers, leaky abstractions)
329
+ - Missing or misused design patterns
330
+ For each: specific principle violated, refactoring suggestion, maintainability impact.`,
331
+ correctness: `Check for:
332
+ - Off-by-one errors, incorrect conditionals, wrong operators
333
+ - Null/undefined handling, empty collections, boundary conditions
334
+ - Race conditions, deadlock potential, state inconsistency
335
+ - Uncaught exceptions, silent failures, incorrect error propagation
336
+ For each: triggering input, expected vs actual behavior.
337
+ For significant bugs: suggest a concrete regression test.`,
338
+ testing: `Check for:
339
+ - Missing test coverage for changed code paths
340
+ - Tests that pass for wrong reasons (tautologies, mocked-away logic)
341
+ - Non-deterministic tests (timing, ordering, randomness)
342
+ - Missing edge case tests (null, empty, boundary, error paths)
343
+ For significant gaps: suggest a concrete test (name, inputs, expected output).`,
344
+ scalability: `Check for:
345
+ - Algorithmic complexity that degrades at scale (provide Big-O)
346
+ - Unbounded growth (queues, caches, in-memory collections)
347
+ - Missing pagination, rate limiting, or backpressure
348
+ - Single points of contention (locks, shared state, single-threaded bottlenecks)
349
+ For each: estimated impact at 10x/100x current load.`,
350
+ maintainability: `Check for:
351
+ - God classes, deep nesting (>3 levels), magic numbers
352
+ - Tight coupling between modules, leaky abstractions
353
+ - Code duplication that should be extracted
354
+ - Missing or misleading comments on non-obvious logic
355
+ For each: specific refactoring suggestion with rationale.`,
356
+ documentation: `Check for:
357
+ - Public API functions missing doc comments
358
+ - Outdated or misleading comments that contradict the code
359
+ - Missing README updates for changed behavior
360
+ - Undocumented configuration, environment variables, or flags
361
+ For each: what specifically should be documented and where.`,
362
+ };
363
+ /**
364
+ * Build the review prompt using minimal, targeted context.
365
+ * No output format constraints — reviewer responds naturally, CC interprets.
366
+ */
367
+ export function buildHandoffPrompt(options) {
368
+ const { handoff } = options;
369
+ const role = options.role || selectRole(handoff.focusAreas);
370
+ const sections = [];
371
+ // SECTION 1: ROLE
372
+ sections.push(`# ROLE: ${role.name}\n\n${role.systemPrompt}`);
373
+ // SECTION 2: REVIEW CHECKLIST (focus-area-specific patterns to look for)
374
+ const focusAreas = handoff.focusAreas;
375
+ if (focusAreas && focusAreas.length > 0) {
376
+ const checklists = focusAreas
377
+ .map(f => FOCUS_CHECKLISTS[f])
378
+ .filter((c) => !!c);
379
+ if (checklists.length > 0) {
380
+ sections.push(`## REVIEW CHECKLIST\n\n${checklists.join('\n\n')}`);
381
+ }
382
+ }
383
+ // SECTION 3: TASK
384
+ sections.push(`## YOUR TASK
385
+
386
+ Review code in \`${handoff.workingDir}\`.
387
+
388
+ **Summary:** ${handoff.summary}${handoff.confidence !== undefined && handoff.confidence < 0.9 ? `\n**CC Confidence:** ${Math.round(handoff.confidence * 100)}% — verify weak areas` : ''}
389
+
390
+ **IMPORTANT:**
391
+ - This is a READ-ONLY review. Do NOT create, modify, or delete any files. Only read files to verify claims.
392
+ - Do NOT assume a git repository exists. Do NOT run git commands. Read files directly from the filesystem.`);
393
+ // SECTION 4: CC'S UNCERTAINTIES
394
+ if (handoff.uncertainties && handoff.uncertainties.length > 0) {
395
+ sections.push(`## CC'S UNCERTAINTIES
396
+
397
+ ${handoff.uncertainties.map((u, i) => `### ${i + 1}. ${u.topic} ${u.severity === 'critical' ? '⚠️' : ''}
398
+ - **Question:** ${u.question}
399
+ ${u.ccAssumption ? `- **CC assumed:** ${u.ccAssumption}` : ''}
400
+ ${u.relevantFiles ? `- **Files:** ${u.relevantFiles.join(', ')}` : ''}`).join('\n\n')}`);
401
+ }
402
+ // SECTION 5: SPECIFIC QUESTIONS
403
+ if (handoff.questions && handoff.questions.length > 0) {
404
+ sections.push(`## QUESTIONS FROM CC
405
+
406
+ ${handoff.questions.map((q, i) => `${i + 1}. **${q.question}**
407
+ ${q.context ? `Context: ${q.context}` : ''}
408
+ ${q.ccGuess ? `CC Guess: ${q.ccGuess}` : ''}`).join('\n')}`);
409
+ }
410
+ // SECTION 6: DECISIONS TO EVALUATE
411
+ if (handoff.decisions && handoff.decisions.length > 0) {
412
+ sections.push(`## DECISIONS TO EVALUATE
413
+
414
+ ${handoff.decisions.map((d, i) => `${i + 1}. **${d.decision}**${d.rationale ? `\n Rationale: ${d.rationale}` : ''}${d.alternatives ? `\n Alternatives: ${d.alternatives.join(', ')}` : ''}`).join('\n')}`);
415
+ }
416
+ // SECTION 7: PRIORITY FILES
417
+ if (handoff.priorityFiles && handoff.priorityFiles.length > 0) {
418
+ sections.push(`## PRIORITY FILES\n\n${handoff.priorityFiles.map(f => `- \`${f}\``).join('\n')}`);
419
+ }
420
+ // SECTION 8: CUSTOM INSTRUCTIONS
421
+ if (handoff.customInstructions) {
422
+ sections.push(`## ADDITIONAL INSTRUCTIONS\n\n${handoff.customInstructions}`);
423
+ }
424
+ return sections.join('\n\n');
425
+ }
426
+ // =============================================================================
427
+ // STRUCTURED ccOutput PARSER
428
+ // =============================================================================
429
+ /**
430
+ * Parse structured ccOutput into Handoff fields.
431
+ *
432
+ * The slash commands tell CC to format its output as:
433
+ * SUMMARY:
434
+ * <text>
435
+ *
436
+ * UNCERTAINTIES (verify these):
437
+ * 1. <text>
438
+ *
439
+ * QUESTIONS:
440
+ * 1. <text>
441
+ *
442
+ * PRIORITY FILES:
443
+ * - <file>
444
+ *
445
+ * If no sections detected, returns { summary: ccOutput } (graceful fallback).
446
+ */
447
+ export function parseStructuredCcOutput(ccOutput) {
448
+ // Quick check: does it look structured? Case-SENSITIVE to avoid matching
449
+ // prose like "Summary: I think..." — slash commands produce ALL-CAPS headers.
450
+ if (!/^SUMMARY[^:\n]*:/m.test(ccOutput)) {
451
+ return { summary: ccOutput };
452
+ }
453
+ // Known section headers — case-SENSITIVE (ALL-CAPS only) to prevent
454
+ // header injection from natural prose starting with "Questions:" etc.
455
+ const KNOWN_HEADERS = ['SUMMARY', 'UNCERTAINTIES', 'QUESTIONS', 'PRIORITY FILES', 'DECISIONS'];
456
+ const headerPattern = new RegExp(`^(${KNOWN_HEADERS.join('|')})[^:\\n]*:`, 'gm' // no 'i' flag — case-sensitive
457
+ );
458
+ // Find all header positions
459
+ const headers = [];
460
+ let match;
461
+ while ((match = headerPattern.exec(ccOutput)) !== null) {
462
+ const raw = match[1].trim();
463
+ const name = KNOWN_HEADERS.find(h => raw.startsWith(h)) || raw;
464
+ headers.push({ name, contentStart: match.index + match[0].length });
465
+ }
466
+ if (headers.length === 0) {
467
+ return { summary: ccOutput };
468
+ }
469
+ // Extract content between headers
470
+ const sections = new Map();
471
+ for (let i = 0; i < headers.length; i++) {
472
+ const start = headers[i].contentStart;
473
+ const end = i + 1 < headers.length
474
+ ? ccOutput.lastIndexOf('\n', headers[i + 1].contentStart - headers[i + 1].name.length - 1)
475
+ : ccOutput.length;
476
+ sections.set(headers[i].name, ccOutput.slice(start, end).trim());
477
+ }
478
+ const rawSummary = sections.get('SUMMARY');
479
+ const result = {
480
+ summary: rawSummary && rawSummary.length > 0 ? rawSummary : ccOutput,
481
+ };
482
+ // Parse uncertainties (numbered or bulleted list)
483
+ const uncertText = sections.get('UNCERTAINTIES');
484
+ if (uncertText) {
485
+ const items = parseListItems(uncertText);
486
+ if (items.length > 0) {
487
+ result.uncertainties = items.map(item => ({
488
+ topic: extractTopic(item),
489
+ question: item,
490
+ }));
491
+ }
492
+ }
493
+ // Parse questions (numbered or bulleted list)
494
+ const questionsText = sections.get('QUESTIONS');
495
+ if (questionsText) {
496
+ const items = parseListItems(questionsText);
497
+ if (items.length > 0) {
498
+ result.questions = items.map(item => ({ question: item }));
499
+ }
500
+ }
501
+ // Parse priority files (bullet or numbered list)
502
+ const filesText = sections.get('PRIORITY FILES');
503
+ if (filesText) {
504
+ const items = parseListItems(filesText);
505
+ if (items.length > 0) {
506
+ result.priorityFiles = items;
507
+ }
508
+ }
509
+ // Parse decisions (numbered or bulleted list)
510
+ const decisionsText = sections.get('DECISIONS');
511
+ if (decisionsText) {
512
+ const items = parseListItems(decisionsText);
513
+ if (items.length > 0) {
514
+ result.decisions = items.map(item => ({ decision: item, rationale: '' }));
515
+ }
516
+ }
517
+ return result;
518
+ }
519
+ /**
520
+ * Extract a short topic from an item — uses first sentence/clause up to 60 chars.
521
+ * Avoids redundant rendering where topic === question.
522
+ */
523
+ function extractTopic(item) {
524
+ // Try first clause (up to first comma, period, dash, or question mark)
525
+ const clauseMatch = item.match(/^(.+?)[,.\-?]/);
526
+ const clause = clauseMatch ? clauseMatch[1].trim() : item;
527
+ if (clause.length <= 60)
528
+ return clause;
529
+ return clause.slice(0, 57) + '...';
530
+ }
531
+ /**
532
+ * Parse a list section that may use numbered ("1. foo") or bulleted ("- foo") format.
533
+ * Supports multi-line continuation for both styles.
534
+ */
535
+ function parseListItems(text) {
536
+ const items = [];
537
+ let current = '';
538
+ for (const line of text.split('\n')) {
539
+ // Match numbered: "1. foo", "2) bar"
540
+ const numbered = line.match(/^\d+[.)]\s+(.+)/);
541
+ // Match bulleted: "- foo", "* bar"
542
+ const bulleted = line.match(/^[-*]\s+(.+)/);
543
+ if (numbered || bulleted) {
544
+ if (current)
545
+ items.push(current.trim());
546
+ current = (numbered || bulleted)[1];
547
+ }
548
+ else if (current && line.trim()) {
549
+ // Continuation line for multi-line items
550
+ current += ' ' + line.trim();
551
+ }
552
+ }
553
+ if (current)
554
+ items.push(current.trim());
555
+ return items;
556
+ }
557
+ // =============================================================================
558
+ // HELPER: Build handoff from simple inputs
559
+ // =============================================================================
560
+ /**
561
+ * Build a handoff from MCP tool inputs.
562
+ *
563
+ * Parses structured sections (SUMMARY, UNCERTAINTIES, QUESTIONS, PRIORITY FILES)
564
+ * from ccOutput when present, populating typed Handoff fields so reviewers
565
+ * receive machine-usable context instead of a single summary blob.
566
+ */
567
+ export function buildSimpleHandoff(workingDir, ccOutput, analyzedFiles, focusAreas, customPrompt) {
568
+ const parsed = parseStructuredCcOutput(ccOutput);
569
+ // Merge analyzedFiles with any priority files parsed from ccOutput (dedup)
570
+ const mergedFiles = dedupStrings([
571
+ ...(parsed.priorityFiles || []),
572
+ ...(analyzedFiles || []),
573
+ ]);
574
+ return {
575
+ workingDir,
576
+ summary: parsed.summary,
577
+ uncertainties: parsed.uncertainties,
578
+ questions: parsed.questions,
579
+ decisions: parsed.decisions,
580
+ priorityFiles: mergedFiles.length > 0 ? mergedFiles : undefined,
581
+ focusAreas,
582
+ customInstructions: customPrompt,
583
+ };
584
+ }
585
+ function dedupStrings(arr) {
586
+ return [...new Set(arr)];
587
+ }
588
+ /**
589
+ * Enhance a simple handoff with uncertainties/questions
590
+ * CC should call this to add its specific concerns
591
+ */
592
+ export function enhanceHandoff(handoff, uncertainties, questions, decisions) {
593
+ return {
594
+ ...handoff,
595
+ uncertainties: uncertainties || handoff.uncertainties,
596
+ questions: questions || handoff.questions,
597
+ decisions: decisions || handoff.decisions,
598
+ };
599
+ }
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Quorum MCP Server
4
+ *
5
+ * Provides tools for getting second-opinion reviews from external AI CLIs
6
+ * (Codex and Gemini) on Claude Code's work.
7
+ *
8
+ * Features:
9
+ * - Single model review (codex_review, gemini_review)
10
+ * - Multi-model parallel review (multi_review)
11
+ * - Structured JSON output with confidence scores
12
+ * - Expert role specialization per focus area
13
+ *
14
+ * Usage:
15
+ * - npx -y @simonren/quorum # Run MCP server (normal usage)
16
+ * - npx -y @simonren/quorum update # Install/update slash commands
17
+ */
18
+ import './adapters/index.js';