@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,220 @@
1
+ /**
2
+ * MCP Tool — multi_consult
3
+ *
4
+ * Asks Codex, Gemini, and Claude (Opus) the same question in parallel.
5
+ * Returns each model's structured 5-section response to CC for synthesis.
6
+ */
7
+ import { z } from 'zod';
8
+ import { realpathSync } from 'fs';
9
+ import { resolve, sep } from 'path';
10
+ import { homedir } from 'os';
11
+ import { getAvailableAdapters } from '../adapters/index.js';
12
+ // =============================================================================
13
+ // SENSITIVE PATH GUARD
14
+ // =============================================================================
15
+ /**
16
+ * Returns the directory's *canonical* absolute path if it's safe to use as a
17
+ * workingDir, or null if it resolves to a sensitive system location. We deny
18
+ * roots like `/`, `/etc`, `~`, `~/.ssh`, etc. — paths *inside* a project root
19
+ * are fine. The check resolves symlinks via realpath so a symlinked alias of
20
+ * a sensitive directory is also caught.
21
+ */
22
+ export function checkSensitiveWorkingDir(input) {
23
+ let resolved;
24
+ try {
25
+ // realpath if it exists; otherwise fall back to resolve() so the standard
26
+ // adapter cwd-existence check produces the user-visible error.
27
+ resolved = realpathSync(input);
28
+ }
29
+ catch {
30
+ resolved = resolve(input);
31
+ }
32
+ const home = homedir();
33
+ const rawDenylist = [
34
+ '/',
35
+ '/etc',
36
+ '/var',
37
+ '/usr',
38
+ '/bin',
39
+ '/sbin',
40
+ '/boot',
41
+ '/root',
42
+ home,
43
+ `${home}${sep}.ssh`,
44
+ `${home}${sep}.aws`,
45
+ `${home}${sep}.config`,
46
+ `${home}${sep}.gnupg`,
47
+ ];
48
+ // Resolve denylist symlinks too (e.g. macOS /etc -> /private/etc) so the
49
+ // resolved-path comparison hits regardless of symlink direction.
50
+ const denylist = new Set();
51
+ for (const path of rawDenylist) {
52
+ denylist.add(path);
53
+ try {
54
+ denylist.add(realpathSync(path));
55
+ }
56
+ catch { /* ignore — path doesn't exist */ }
57
+ }
58
+ if (denylist.has(resolved)) {
59
+ return { ok: false, reason: `workingDir resolves to a sensitive path: ${resolved}` };
60
+ }
61
+ return { ok: true, resolved };
62
+ }
63
+ // =============================================================================
64
+ // SECTION VALIDATION
65
+ // =============================================================================
66
+ const REQUIRED_SECTIONS = [
67
+ 'Recommendation',
68
+ 'Reasoning',
69
+ 'Tradeoffs',
70
+ 'Risks',
71
+ 'Open questions for the asker',
72
+ ];
73
+ /**
74
+ * Lightweight check for the 5 expected `## …` headers in a model's consult
75
+ * response. Behaviors:
76
+ * - Strips fenced code blocks first so a quoted format-example skeleton
77
+ * doesn't falsely satisfy the check.
78
+ * - Requires the section name as a word boundary at the start of an H2 line,
79
+ * but tolerates trailing decoration (colon, em-dash continuation, etc.).
80
+ * - Case-sensitive on the section name. Bare bold (`**Recommendation**`),
81
+ * wrong level (`### Recommendation`), and ALL-CAPS variants all count as
82
+ * missing — that's the signal we want CC to see when models drift.
83
+ */
84
+ export function validateConsultSections(output) {
85
+ // Remove fenced code blocks so headers inside them don't satisfy the regex.
86
+ const stripped = output.replace(/```[\s\S]*?```/g, '');
87
+ const missing = [];
88
+ for (const section of REQUIRED_SECTIONS) {
89
+ // Match the exact section name at the start of an H2 header line.
90
+ // `\b` after the name allows `:`, `—`, `-`, whitespace+more — but not
91
+ // suffixed letters/digits (which would change the section name itself).
92
+ const pattern = new RegExp(`^##\\s+${escapeRegex(section)}\\b[^\\n]*$`, 'm');
93
+ if (!pattern.test(stripped)) {
94
+ missing.push(section);
95
+ }
96
+ }
97
+ return { missing };
98
+ }
99
+ function escapeRegex(s) {
100
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
101
+ }
102
+ // =============================================================================
103
+ // INPUT SCHEMA
104
+ // =============================================================================
105
+ export const ConsultInputSchema = z.object({
106
+ workingDir: z.string().describe('Working directory for the CLI to operate in'),
107
+ question: z.string().describe('CC-composed self-contained question for the panel'),
108
+ relevantFiles: z.array(z.string()).optional().describe('CC-triaged file subset for code-grounded questions'),
109
+ customPrompt: z.string()
110
+ .optional()
111
+ // Reject any literal `<user-steering` or `</user-steering` so a steering
112
+ // value cannot escape the prompt envelope and inject instructions.
113
+ .refine(v => !v || !/<\/?user-steering/i.test(v), {
114
+ message: 'customPrompt must not contain <user-steering> tags',
115
+ })
116
+ .describe('Free-form steering from $ARGUMENTS'),
117
+ reasoningEffort: z.enum(['high', 'xhigh']).optional().describe("Codex reasoning effort (default: 'xhigh' for consult)"),
118
+ serviceTier: z.enum(['default', 'fast', 'flex']).optional().describe("Codex service tier (default: 'fast')"),
119
+ });
120
+ function toConsultRequest(input) {
121
+ return {
122
+ workingDir: input.workingDir,
123
+ question: input.question,
124
+ relevantFiles: input.relevantFiles,
125
+ customPrompt: input.customPrompt,
126
+ reasoningEffort: input.reasoningEffort,
127
+ serviceTier: input.serviceTier,
128
+ };
129
+ }
130
+ function formatOutcome(outcome) {
131
+ const { adapter, result } = outcome;
132
+ const name = adapter.getCapabilities().name;
133
+ if (!result.success) {
134
+ const emoji = {
135
+ cli_not_found: '❌', timeout: '⏱️', rate_limit: '🚫', auth_error: '🔐', cli_error: '❌',
136
+ };
137
+ let msg = `## ${name}\n${emoji[result.error.type] || '❌'} **${result.error.type}**: ${result.error.message}`;
138
+ if (result.suggestion)
139
+ msg += `\n\n💡 ${result.suggestion}`;
140
+ return msg;
141
+ }
142
+ const drift = validateConsultSections(result.output);
143
+ const driftLine = drift.missing.length > 0
144
+ ? `⚠️ Format drift: missing sections [${drift.missing.join(', ')}]\n\n`
145
+ : '';
146
+ return `## ${name}\n**Execution Time:** ${(result.executionTimeMs / 1000).toFixed(1)}s\n\n${driftLine}${result.output}`;
147
+ }
148
+ export async function handleMultiConsult(input) {
149
+ // Sensitive-cwd guard runs before any adapter dispatch — direct MCP callers
150
+ // can't bypass the slash-command body's refusal by skipping CC.
151
+ const guard = checkSensitiveWorkingDir(input.workingDir);
152
+ if (!guard.ok) {
153
+ return {
154
+ content: [{
155
+ type: 'text',
156
+ text: `❌ Refused: ${guard.reason}\n\nInvoke /multi-consult from a project root, not a sensitive system path.`,
157
+ }],
158
+ };
159
+ }
160
+ const request = toConsultRequest(input);
161
+ const adapters = await getAvailableAdapters();
162
+ if (adapters.length === 0) {
163
+ return {
164
+ content: [{
165
+ type: 'text',
166
+ text: '❌ No AI CLIs found.\n\nInstall at least one:\n - Codex: npm install -g @openai/codex-cli\n - Gemini: npm install -g @google/gemini-cli',
167
+ }],
168
+ };
169
+ }
170
+ // Promise.allSettled — a rejected adapter must NOT collapse the whole call.
171
+ const settled = await Promise.allSettled(adapters.map((adapter) => adapter.runConsult(request).then((result) => ({ adapter, result }))));
172
+ const outcomes = settled.map((s, i) => {
173
+ if (s.status === 'fulfilled')
174
+ return s.value;
175
+ const message = s.reason instanceof Error ? s.reason.message : String(s.reason);
176
+ return {
177
+ adapter: adapters[i],
178
+ result: {
179
+ success: false,
180
+ error: { type: 'cli_error', message },
181
+ suggestion: 'Adapter rejected — see error above',
182
+ executionTimeMs: 0,
183
+ },
184
+ };
185
+ });
186
+ const allFailed = outcomes.every((o) => !o.result.success);
187
+ const someFailed = outcomes.some((o) => !o.result.success);
188
+ const lines = [];
189
+ if (allFailed)
190
+ lines.push('## Multi-Consult ❌ All Failed\n');
191
+ else if (someFailed)
192
+ lines.push('## Multi-Consult ⚠️ Partial Success\n');
193
+ else
194
+ lines.push('## Multi-Consult ✓\n');
195
+ lines.push(`**Models:** ${adapters.map((a) => a.id).join(', ')}\n`);
196
+ for (const outcome of outcomes) {
197
+ lines.push(formatOutcome(outcome));
198
+ lines.push('');
199
+ }
200
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
201
+ }
202
+ // =============================================================================
203
+ // TOOL DEFINITION
204
+ // =============================================================================
205
+ export const MULTI_CONSULT_TOOL_DEFINITION = {
206
+ name: 'multi_consult',
207
+ description: "Use when asking the panel for guidance, recommendation, or approach (no prior CC-produced work to review). Input shape: 'question' only — no 'ccOutput'. For reviewing existing CC-produced work (plan, findings, code), use 'multi_review' (which requires 'ccOutput'). The discriminator is the shape of the input, not the user's phrasing.",
208
+ inputSchema: {
209
+ type: 'object',
210
+ properties: {
211
+ workingDir: { type: 'string', description: 'Working directory for the CLI to operate in' },
212
+ question: { type: 'string', description: 'CC-composed self-contained question for the panel' },
213
+ relevantFiles: { type: 'array', items: { type: 'string' }, description: 'CC-triaged file subset for code-grounded questions' },
214
+ customPrompt: { type: 'string', description: 'Free-form steering from $ARGUMENTS' },
215
+ reasoningEffort: { type: 'string', enum: ['high', 'xhigh'], description: "Codex reasoning effort (default: 'xhigh' for consult)" },
216
+ serviceTier: { type: 'string', enum: ['default', 'fast', 'flex'], description: "Codex service tier (default: 'fast')" },
217
+ },
218
+ required: ['workingDir', 'question'],
219
+ },
220
+ };
@@ -0,0 +1,91 @@
1
+ /**
2
+ * MCP Tool Implementations — Review Tools
3
+ *
4
+ * Returns raw reviewer text to CC. No JSON parsing, no reformatting.
5
+ * CC handles interpretation and synthesis.
6
+ */
7
+ import { z } from 'zod';
8
+ export declare const ReviewInputSchema: z.ZodObject<{
9
+ workingDir: z.ZodString;
10
+ ccOutput: z.ZodString;
11
+ outputType: z.ZodEnum<["plan", "findings", "analysis", "proposal"]>;
12
+ analyzedFiles: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
13
+ focusAreas: z.ZodOptional<z.ZodArray<z.ZodEnum<["security", "performance", "architecture", "correctness", "maintainability", "scalability", "testing", "documentation"]>, "many">>;
14
+ customPrompt: z.ZodOptional<z.ZodString>;
15
+ reasoningEffort: z.ZodOptional<z.ZodEnum<["high", "xhigh"]>>;
16
+ serviceTier: z.ZodOptional<z.ZodEnum<["default", "fast", "flex"]>>;
17
+ }, "strip", z.ZodTypeAny, {
18
+ workingDir: string;
19
+ ccOutput: string;
20
+ outputType: "plan" | "findings" | "analysis" | "proposal";
21
+ reasoningEffort?: "high" | "xhigh" | undefined;
22
+ serviceTier?: "default" | "fast" | "flex" | undefined;
23
+ customPrompt?: string | undefined;
24
+ focusAreas?: ("security" | "performance" | "architecture" | "correctness" | "maintainability" | "scalability" | "testing" | "documentation")[] | undefined;
25
+ analyzedFiles?: string[] | undefined;
26
+ }, {
27
+ workingDir: string;
28
+ ccOutput: string;
29
+ outputType: "plan" | "findings" | "analysis" | "proposal";
30
+ reasoningEffort?: "high" | "xhigh" | undefined;
31
+ serviceTier?: "default" | "fast" | "flex" | undefined;
32
+ customPrompt?: string | undefined;
33
+ focusAreas?: ("security" | "performance" | "architecture" | "correctness" | "maintainability" | "scalability" | "testing" | "documentation")[] | undefined;
34
+ analyzedFiles?: string[] | undefined;
35
+ }>;
36
+ export type ReviewInput = z.infer<typeof ReviewInputSchema>;
37
+ export declare function handleMultiReview(input: ReviewInput): Promise<{
38
+ content: Array<{
39
+ type: 'text';
40
+ text: string;
41
+ }>;
42
+ }>;
43
+ export declare const TOOL_DEFINITIONS: {
44
+ multi_review: {
45
+ name: string;
46
+ description: string;
47
+ inputSchema: {
48
+ type: string;
49
+ properties: {
50
+ workingDir: {
51
+ type: string;
52
+ description: string;
53
+ };
54
+ ccOutput: {
55
+ type: string;
56
+ description: string;
57
+ };
58
+ outputType: {
59
+ type: string;
60
+ enum: string[];
61
+ description: string;
62
+ };
63
+ analyzedFiles: {
64
+ type: string;
65
+ items: {
66
+ type: string;
67
+ };
68
+ description: string;
69
+ };
70
+ focusAreas: {
71
+ type: string;
72
+ items: {
73
+ type: string;
74
+ enum: string[];
75
+ };
76
+ description: string;
77
+ };
78
+ customPrompt: {
79
+ type: string;
80
+ description: string;
81
+ };
82
+ serviceTier: {
83
+ type: string;
84
+ enum: string[];
85
+ description: string;
86
+ };
87
+ };
88
+ required: string[];
89
+ };
90
+ };
91
+ };
@@ -0,0 +1,117 @@
1
+ /**
2
+ * MCP Tool Implementations — Review Tools
3
+ *
4
+ * Returns raw reviewer text to CC. No JSON parsing, no reformatting.
5
+ * CC handles interpretation and synthesis.
6
+ */
7
+ import { z } from 'zod';
8
+ import { getAvailableAdapters, } from '../adapters/index.js';
9
+ // =============================================================================
10
+ // INPUT SCHEMAS
11
+ // =============================================================================
12
+ export const ReviewInputSchema = z.object({
13
+ workingDir: z.string().describe('Working directory for the CLI to operate in'),
14
+ ccOutput: z.string().describe("Claude Code's output to review (findings, plan, analysis)"),
15
+ outputType: z.enum(['plan', 'findings', 'analysis', 'proposal']).describe('Type of output being reviewed'),
16
+ analyzedFiles: z.array(z.string()).optional().describe('File paths that CC analyzed'),
17
+ focusAreas: z.array(z.enum([
18
+ 'security', 'performance', 'architecture', 'correctness',
19
+ 'maintainability', 'scalability', 'testing', 'documentation'
20
+ ])).optional().describe('Areas to focus the review on'),
21
+ customPrompt: z.string().optional().describe('Custom instructions for the reviewer'),
22
+ reasoningEffort: z.enum(['high', 'xhigh']).optional().describe('Codex reasoning effort level (default: high, use xhigh for deeper analysis)'),
23
+ serviceTier: z.enum(['default', 'fast', 'flex']).optional().describe('Codex service tier (default when omitted: fast = priority processing, ~2x cost; flex = 50% cheaper/slower; default = API default tier)')
24
+ });
25
+ // =============================================================================
26
+ // HELPERS
27
+ // =============================================================================
28
+ function toReviewRequest(input) {
29
+ return {
30
+ workingDir: input.workingDir,
31
+ ccOutput: input.ccOutput,
32
+ outputType: input.outputType,
33
+ analyzedFiles: input.analyzedFiles,
34
+ focusAreas: input.focusAreas,
35
+ customPrompt: input.customPrompt,
36
+ reasoningEffort: input.reasoningEffort,
37
+ serviceTier: input.serviceTier,
38
+ };
39
+ }
40
+ function formatResult(result, modelName) {
41
+ if (!result.success) {
42
+ const emoji = {
43
+ cli_not_found: '❌', timeout: '⏱️', rate_limit: '🚫',
44
+ auth_error: '🔐', cli_error: '❌',
45
+ };
46
+ let msg = `${emoji[result.error.type] || '❌'} **${result.error.type}**: ${result.error.message}`;
47
+ if (result.suggestion)
48
+ msg += `\n\n💡 ${result.suggestion}`;
49
+ return msg;
50
+ }
51
+ return `## ${modelName} Review\n\n**Execution Time:** ${(result.executionTimeMs / 1000).toFixed(1)}s\n\n${result.output}`;
52
+ }
53
+ // =============================================================================
54
+ // MULTI-MODEL HANDLER
55
+ // =============================================================================
56
+ export async function handleMultiReview(input) {
57
+ const request = toReviewRequest(input);
58
+ const availableAdapters = await getAvailableAdapters();
59
+ if (availableAdapters.length === 0) {
60
+ return { content: [{ type: 'text', text: '❌ No AI CLIs found.\n\nInstall at least one:\n - Codex: npm install -g @openai/codex-cli\n - Gemini: npm install -g @google/gemini-cli' }] };
61
+ }
62
+ // Spawn 2 reviews per adapter: standard + adversarial (all in parallel)
63
+ // customPrompt steers the adversarial focus only — strip it from standard pass to avoid bias
64
+ const { customPrompt, ...standardRequest } = request;
65
+ const reviewPromises = availableAdapters.flatMap((adapter) => [
66
+ adapter.runReview({ ...standardRequest }).then(result => ({ adapter, result, mode: 'standard' })),
67
+ adapter.runReview({ ...request, reviewMode: 'adversarial' }).then(result => ({ adapter, result, mode: 'adversarial' })),
68
+ ]);
69
+ const results = await Promise.all(reviewPromises);
70
+ const standardResults = results.filter(r => r.mode === 'standard');
71
+ const adversarialResults = results.filter(r => r.mode === 'adversarial');
72
+ const allFailed = results.every(r => !r.result.success);
73
+ const someFailed = results.some(r => !r.result.success);
74
+ const lines = [];
75
+ if (allFailed)
76
+ lines.push('## Multi-Model Review ❌ All Failed\n');
77
+ else if (someFailed)
78
+ lines.push('## Multi-Model Review ⚠️ Partial Success\n');
79
+ else
80
+ lines.push('## Multi-Model Review ✓\n');
81
+ lines.push(`**Models:** ${availableAdapters.map(a => a.id).join(', ')} (standard + adversarial)\n`);
82
+ // Standard section
83
+ lines.push('## Standard Review Findings\n');
84
+ for (const { adapter, result } of standardResults) {
85
+ lines.push(formatResult(result, adapter.getCapabilities().name));
86
+ lines.push('');
87
+ }
88
+ // Adversarial section
89
+ lines.push('## Challenge Review Findings\n');
90
+ for (const { adapter, result } of adversarialResults) {
91
+ lines.push(formatResult(result, `${adapter.getCapabilities().name} (Adversarial)`));
92
+ lines.push('');
93
+ }
94
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
95
+ }
96
+ // =============================================================================
97
+ // TOOL DEFINITIONS
98
+ // =============================================================================
99
+ export const TOOL_DEFINITIONS = {
100
+ multi_review: {
101
+ name: 'multi_review',
102
+ description: "Use when reviewing existing CC-produced work (plan, findings, code). Requires 'ccOutput' — CC's prior output to evaluate. Runs parallel standard AND adversarial reviews from all available models. For asking the panel an open question with no prior CC-produced work to review, use 'multi_consult' instead. The discriminator is the shape of the input, not the user's phrasing.",
103
+ inputSchema: {
104
+ type: 'object',
105
+ properties: {
106
+ workingDir: { type: 'string', description: 'Working directory for the CLI to operate in' },
107
+ ccOutput: { type: 'string', description: "Claude Code's output to review (findings, plan, analysis)" },
108
+ outputType: { type: 'string', enum: ['plan', 'findings', 'analysis', 'proposal'], description: 'Type of output being reviewed' },
109
+ analyzedFiles: { type: 'array', items: { type: 'string' }, description: 'File paths that CC analyzed' },
110
+ focusAreas: { type: 'array', items: { type: 'string', enum: ['security', 'performance', 'architecture', 'correctness', 'maintainability', 'scalability', 'testing', 'documentation'] }, description: 'Areas to focus the review on' },
111
+ customPrompt: { type: 'string', description: 'Custom instructions for standard review + adversarial focus steering' },
112
+ serviceTier: { type: 'string', enum: ['default', 'fast', 'flex'], description: 'Codex service tier — only applies to Codex. Omit for fast default; fast = priority ~2x cost, flex = 50% cheaper/slower, default = API default tier.' }
113
+ },
114
+ required: ['workingDir', 'ccOutput', 'outputType']
115
+ }
116
+ },
117
+ };
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Types for AI Reviewer MCP Server
3
+ */
4
+ export type OutputType = 'plan' | 'findings' | 'analysis' | 'proposal';
5
+ export type FocusArea = 'security' | 'performance' | 'architecture' | 'correctness' | 'maintainability' | 'scalability' | 'testing' | 'documentation';
6
+ export type CliType = 'codex' | 'gemini' | 'claude';
7
+ export type ReasoningEffort = 'high' | 'xhigh';
8
+ export type ServiceTier = 'default' | 'fast' | 'flex';
9
+ export interface FeedbackRequest {
10
+ workingDir: string;
11
+ ccOutput: string;
12
+ outputType: OutputType;
13
+ analyzedFiles?: string[];
14
+ focusAreas?: FocusArea[];
15
+ customPrompt?: string;
16
+ reasoningEffort?: ReasoningEffort;
17
+ serviceTier?: ServiceTier;
18
+ }
19
+ export interface FeedbackSuccess {
20
+ success: true;
21
+ feedback: string;
22
+ model: CliType;
23
+ }
24
+ export interface FeedbackFailure {
25
+ success: false;
26
+ error: FeedbackError;
27
+ suggestion?: string;
28
+ model: CliType;
29
+ }
30
+ export type FeedbackResult = FeedbackSuccess | FeedbackFailure;
31
+ export type FeedbackError = {
32
+ type: 'cli_not_found';
33
+ cli: CliType;
34
+ installCmd: string;
35
+ } | {
36
+ type: 'timeout';
37
+ cli: CliType;
38
+ durationMs: number;
39
+ } | {
40
+ type: 'rate_limit';
41
+ cli: CliType;
42
+ retryAfterMs?: number;
43
+ } | {
44
+ type: 'auth_error';
45
+ cli: CliType;
46
+ message: string;
47
+ } | {
48
+ type: 'invalid_response';
49
+ cli: CliType;
50
+ rawOutput: string;
51
+ } | {
52
+ type: 'cli_error';
53
+ cli: CliType;
54
+ exitCode: number;
55
+ stderr: string;
56
+ };
57
+ export interface MultiFeedbackResult {
58
+ successful: Array<{
59
+ model: CliType;
60
+ feedback: string;
61
+ }>;
62
+ failed: Array<{
63
+ model: CliType;
64
+ error: FeedbackError;
65
+ }>;
66
+ partialSuccess: boolean;
67
+ allFailed: boolean;
68
+ }
69
+ export interface CliStatus {
70
+ codex: boolean;
71
+ gemini: boolean;
72
+ claude: boolean;
73
+ }
74
+ export interface StructuredFeedback {
75
+ agreements: Array<{
76
+ finding: string;
77
+ reason: string;
78
+ }>;
79
+ disagreements: Array<{
80
+ finding: string;
81
+ reason: string;
82
+ correction: string;
83
+ }>;
84
+ additions: Array<{
85
+ finding: string;
86
+ location: string;
87
+ impact: string;
88
+ }>;
89
+ alternatives: Array<{
90
+ topic: string;
91
+ alternative: string;
92
+ tradeoffs: string;
93
+ }>;
94
+ riskAssessment: {
95
+ level: 'Low' | 'Medium' | 'High';
96
+ reason: string;
97
+ };
98
+ }
99
+ export interface ReviewerPersona {
100
+ name: string;
101
+ focus: string;
102
+ style: string;
103
+ }
104
+ export declare const REVIEWER_PERSONAS: Record<CliType, ReviewerPersona>;
105
+ export declare const FOCUS_AREA_DESCRIPTIONS: Record<FocusArea, string>;
package/dist/types.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Types for AI Reviewer MCP Server
3
+ */
4
+ export const REVIEWER_PERSONAS = {
5
+ codex: {
6
+ name: 'Codex',
7
+ focus: 'correctness, edge cases, performance',
8
+ style: 'Apply pragmatic skepticism - verify before agreeing.'
9
+ },
10
+ gemini: {
11
+ name: 'Gemini',
12
+ focus: 'design patterns, scalability, tech debt',
13
+ style: 'Think holistically - consider broader context.'
14
+ },
15
+ claude: {
16
+ name: 'Claude',
17
+ focus: 'deep analysis, correctness, security, architecture',
18
+ style: 'Fresh perspective with clean context - challenge assumptions.'
19
+ }
20
+ };
21
+ // Focus area descriptions
22
+ export const FOCUS_AREA_DESCRIPTIONS = {
23
+ security: 'Vulnerabilities, auth, input validation',
24
+ performance: 'Speed, memory, efficiency',
25
+ architecture: 'Design patterns, structure, coupling',
26
+ correctness: 'Logic errors, edge cases, bugs',
27
+ maintainability: 'Code clarity, documentation, complexity',
28
+ scalability: 'Load handling, bottlenecks',
29
+ testing: 'Test coverage, test quality',
30
+ documentation: 'Comments, docs, API docs'
31
+ };
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@simonren/quorum",
3
+ "version": "0.7.0",
4
+ "description": "MCP server for Claude Code — a quorum of AI models (Codex, Gemini, Claude) for adversarial review and consultation, synthesized by Claude Code",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "quorum": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist/**/*",
12
+ "commands/**/*",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "start": "node dist/index.js",
19
+ "dev": "tsc --watch",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "keywords": [
25
+ "mcp",
26
+ "claude",
27
+ "claude-code",
28
+ "codex",
29
+ "gemini",
30
+ "quorum",
31
+ "ai-review",
32
+ "ai-consult",
33
+ "code-review"
34
+ ],
35
+ "author": "SimonRen",
36
+ "license": "MIT",
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/SimonRen/quorum.git"
43
+ },
44
+ "homepage": "https://github.com/SimonRen/quorum#readme",
45
+ "dependencies": {
46
+ "@modelcontextprotocol/sdk": "^1.0.0",
47
+ "zod": "^3.22.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^20.0.0",
51
+ "typescript": "^5.0.0",
52
+ "vitest": "^2.0.0"
53
+ }
54
+ }