@rvry/mcp 0.1.5 → 0.1.6

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.
@@ -2,7 +2,7 @@
2
2
  description: Deep structured analysis for high-stakes questions via RVRY
3
3
  argument-hint: [--auto] <question>
4
4
  allowed-tools:
5
- - mcp__RVRY__RVRY_deepthink
5
+ - mcp__rvry-dev__deepthink
6
6
  - AskUserQuestion
7
7
  ---
8
8
 
@@ -17,38 +17,78 @@ allowed-tools:
17
17
 
18
18
  ## Phase 1: Start Session
19
19
 
20
- Call `mcp__RVRY__RVRY_deepthink` with `{ "input": "<cleaned input>", "skipScoping": <true if AUTO_MODE> }`.
20
+ Call `mcp__rvry-dev__deepthink` with `{ "input": "<cleaned input>", "skipScoping": <true if AUTO_MODE> }`.
21
21
 
22
22
  ### If status is "scoping" (and not AUTO_MODE):
23
23
 
24
24
  The engine has returned scoping questions to help calibrate the analysis.
25
25
 
26
- For each question in `scopingQuestions`:
27
- - Present the question and its options to the user via `AskUserQuestion`
28
- - Format as: the question text, followed by the options (label + description)
26
+ **IMPORTANT: You MUST call AskUserQuestion as a tool to collect answers. Do NOT render the questions as plain text — the user's answers will be lost.**
29
27
 
30
- After collecting all answers, format them as a brief context summary (e.g., "Stakes: High. Looking for: risks and failure modes.") and call `mcp__RVRY__RVRY_deepthink` with `{ "input": "<formatted scoping answers>", "sessionId": "<sessionId from previous response>" }`.
28
+ Call `AskUserQuestion` with the scoping questions mapped to its format. Each `scopingQuestions` entry becomes a question:
29
+
30
+ ```
31
+ AskUserQuestion({
32
+ questions: scopingQuestions.map(sq => ({
33
+ question: sq.question,
34
+ header: sq.question.slice(0, 12), // short label
35
+ options: sq.options.map(o => ({ label: o.label, description: o.description })),
36
+ multiSelect: false
37
+ }))
38
+ })
39
+ ```
40
+
41
+ After collecting all answers from AskUserQuestion, format them as a brief context summary (e.g., "Stakes: High. Looking for: risks and failure modes.") and call `mcp__rvry-dev__deepthink` with `{ "input": "<formatted scoping answers>", "sessionId": "<sessionId from previous response>" }`.
31
42
 
32
43
  ### If status is "active":
33
44
 
34
45
  Proceed to Phase 2.
35
46
 
47
+
36
48
  ## Phase 2: Analysis Loop
37
49
 
38
50
  Repeat until `status === "complete"`:
39
51
 
40
- 1. Read the engine's `prompt` -- this is the analytical framing for this round
41
- 2. Read the engine's `instruction` -- this tells you what to focus on
42
- 3. Present the analytical framing to the user before your analysis
43
- 4. Perform your analysis based on the prompt and instruction
44
- 5. Call `mcp__RVRY__RVRY_deepthink` with `{ "input": "<your analysis>", "sessionId": "<sessionId>" }`
52
+ 1. Read the engine's `prompt` and `instruction` these guide YOUR analysis. **Do not show either to the user.**
53
+ 2. Show the user a brief status line:
54
+ - Format: `Round {round} {what you're doing this round}`
55
+ - Example: `Round 3 stress-testing the current position`
56
+ - Derive the description from the engine's phase indicator or your own summary of the round's focus. One line only.
57
+ 3. Perform your analysis in your internal reasoning. The engine's prompt contains analytical framings, self-check questions, and constraint tracking — use all of these to guide your thinking, but they are YOUR instructions, not user output.
58
+ 4. In your internal reasoning, end your analysis with the constraint update block the engine expects:
59
+ ```
60
+ Constraint Updates
61
+ RESOLVE: C1, C2
62
+ ACKNOWLEDGE: C3
63
+ DEFER: C4 | <reason>
64
+ MILESTONE: <milestone> | <evidence>
65
+ End Constraint Updates
66
+ ```
67
+ 5. Call the engine tool with your full analysis (including constraint updates) as the `input`. **The full analysis text goes ONLY in the tool call, never in visible output.**
68
+
69
+ ### What NEVER appears in user-visible output:
70
+ - The engine's `prompt` field content
71
+ - Constraint tables (`C1 [ACTIVE] FORWARD: ...`)
72
+ - Constraint update blocks (`RESOLVE: C1`, `MILESTONE: ...`)
73
+ - Self-check questions or responses
74
+ - Gate verdicts (`SOFT_FAIL`, `HARD_FAIL`, `PASS`)
75
+ - Milestone markers
76
+ - Protocol surface elements (session awareness text, phase indicators from engine)
77
+
78
+ ### What the user sees per round:
79
+ - The one-line status (step 2 above)
80
+ - Nothing else until harvest
45
81
 
46
82
  ## Phase 3: Harvest
47
83
 
48
- When `status === "complete"` and `harvest` is present:
84
+ When `status === "complete"`:
85
+
86
+ Synthesize the analysis for the user based on your accumulated reasoning across all rounds. Do NOT simply echo the engine's `harvest` fields — they are structured data, not a finished presentation.
49
87
 
50
88
  Present to the user:
51
- - **Summary**: harvest.summary
52
- - **Key Findings**: harvest.keyFindings as bullet list
53
- - **Open Questions**: harvest.openQuestions (if any)
54
- - **Suggested Next Steps**: harvest.followUps with question + rationale
89
+ 1. **Default Starting Point**: What the obvious answer was and why it was insufficient
90
+ 2. **Key Findings**: The non-obvious insights that emerged, as a bullet list. Each finding should be a substantive sentence, not a label.
91
+ 3. **Open Questions**: What remains genuinely uncertain (if any)
92
+ 4. **Suggested Next Steps**: Concrete follow-up actions or investigations
93
+
94
+ Use the engine's `harvest.summary`, `harvest.keyFindings`, `harvest.openQuestions`, and `harvest.followUps` as source material, but write the synthesis in your own words with the depth your analysis produced. The harvest fields are often terse — your synthesis should reflect the full depth of the multi-round analysis.
@@ -2,7 +2,7 @@
2
2
  description: Structured decision-making via RVRY
3
3
  argument-hint: [--auto] <problem or decision>
4
4
  allowed-tools:
5
- - mcp__RVRY__RVRY_problem_solve
5
+ - mcp__rvry-dev__problem_solve
6
6
  - AskUserQuestion
7
7
  ---
8
8
 
@@ -17,38 +17,78 @@ allowed-tools:
17
17
 
18
18
  ## Phase 1: Start Session
19
19
 
20
- Call `mcp__RVRY__RVRY_problem_solve` with `{ "input": "<cleaned input>", "skipScoping": <true if AUTO_MODE> }`.
20
+ Call `mcp__rvry-dev__problem_solve` with `{ "input": "<cleaned input>", "skipScoping": <true if AUTO_MODE> }`.
21
21
 
22
22
  ### If status is "scoping" (and not AUTO_MODE):
23
23
 
24
24
  The engine has returned scoping questions to help calibrate the analysis.
25
25
 
26
- For each question in `scopingQuestions`:
27
- - Present the question and its options to the user via `AskUserQuestion`
28
- - Format as: the question text, followed by the options (label + description)
26
+ **IMPORTANT: You MUST call AskUserQuestion as a tool to collect answers. Do NOT render the questions as plain text — the user's answers will be lost.**
29
27
 
30
- After collecting all answers, format them as a brief context summary (e.g., "Options: multiple, need to narrow. Priority: risk minimization.") and call `mcp__RVRY__RVRY_problem_solve` with `{ "input": "<formatted scoping answers>", "sessionId": "<sessionId from previous response>" }`.
28
+ Call `AskUserQuestion` with the scoping questions mapped to its format. Each `scopingQuestions` entry becomes a question:
29
+
30
+ ```
31
+ AskUserQuestion({
32
+ questions: scopingQuestions.map(sq => ({
33
+ question: sq.question,
34
+ header: sq.question.slice(0, 12), // short label
35
+ options: sq.options.map(o => ({ label: o.label, description: o.description })),
36
+ multiSelect: false
37
+ }))
38
+ })
39
+ ```
40
+
41
+ After collecting all answers from AskUserQuestion, format them as a brief context summary (e.g., "Options: multiple, need to narrow. Priority: risk minimization.") and call `mcp__rvry-dev__problem_solve` with `{ "input": "<formatted scoping answers>", "sessionId": "<sessionId from previous response>" }`.
31
42
 
32
43
  ### If status is "active":
33
44
 
34
45
  Proceed to Phase 2.
35
46
 
47
+
36
48
  ## Phase 2: Analysis Loop
37
49
 
38
50
  Repeat until `status === "complete"`:
39
51
 
40
- 1. Read the engine's `prompt` -- this is the analytical framing for this round
41
- 2. Read the engine's `instruction` -- this tells you what to focus on
42
- 3. Present the analytical framing to the user before your analysis
43
- 4. Perform your analysis based on the prompt and instruction
44
- 5. Call `mcp__RVRY__RVRY_problem_solve` with `{ "input": "<your analysis>", "sessionId": "<sessionId>" }`
52
+ 1. Read the engine's `prompt` and `instruction` these guide YOUR analysis. **Do not show either to the user.**
53
+ 2. Show the user a brief status line:
54
+ - Format: `Round {round} {what you're doing this round}`
55
+ - Example: `Round 3 stress-testing the current position`
56
+ - Derive the description from the engine's phase indicator or your own summary of the round's focus. One line only.
57
+ 3. Perform your analysis in your internal reasoning. The engine's prompt contains analytical framings, self-check questions, and constraint tracking — use all of these to guide your thinking, but they are YOUR instructions, not user output.
58
+ 4. In your internal reasoning, end your analysis with the constraint update block the engine expects:
59
+ ```
60
+ Constraint Updates
61
+ RESOLVE: C1, C2
62
+ ACKNOWLEDGE: C3
63
+ DEFER: C4 | <reason>
64
+ MILESTONE: <milestone> | <evidence>
65
+ End Constraint Updates
66
+ ```
67
+ 5. Call the engine tool with your full analysis (including constraint updates) as the `input`. **The full analysis text goes ONLY in the tool call, never in visible output.**
68
+
69
+ ### What NEVER appears in user-visible output:
70
+ - The engine's `prompt` field content
71
+ - Constraint tables (`C1 [ACTIVE] FORWARD: ...`)
72
+ - Constraint update blocks (`RESOLVE: C1`, `MILESTONE: ...`)
73
+ - Self-check questions or responses
74
+ - Gate verdicts (`SOFT_FAIL`, `HARD_FAIL`, `PASS`)
75
+ - Milestone markers
76
+ - Protocol surface elements (session awareness text, phase indicators from engine)
77
+
78
+ ### What the user sees per round:
79
+ - The one-line status (step 2 above)
80
+ - Nothing else until harvest
45
81
 
46
82
  ## Phase 3: Harvest
47
83
 
48
- When `status === "complete"` and `harvest` is present:
84
+ When `status === "complete"`:
85
+
86
+ Synthesize the analysis for the user based on your accumulated reasoning across all rounds. Do NOT simply echo the engine's `harvest` fields — they are structured data, not a finished presentation.
49
87
 
50
88
  Present to the user:
51
- - **Summary**: harvest.summary
52
- - **Key Findings**: harvest.keyFindings as bullet list
53
- - **Open Questions**: harvest.openQuestions (if any)
54
- - **Suggested Next Steps**: harvest.followUps with question + rationale
89
+ 1. **Default Starting Point**: What the obvious answer was and why it was insufficient
90
+ 2. **Key Findings**: The non-obvious insights that emerged, as a bullet list. Each finding should be a substantive sentence, not a label.
91
+ 3. **Open Questions**: What remains genuinely uncertain (if any)
92
+ 4. **Suggested Next Steps**: Concrete follow-up actions or investigations
93
+
94
+ Use the engine's `harvest.summary`, `harvest.keyFindings`, `harvest.openQuestions`, and `harvest.followUps` as source material, but write the synthesis in your own words with the depth your analysis produced. The harvest fields are often terse — your synthesis should reflect the full depth of the multi-round analysis.
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * Thin client that proxies tool calls to the RVRY engine HTTP API.
6
6
  * Auth via RVRY_TOKEN env var (static rvry_ token).
7
7
  *
8
- * Exposes 5 tools (RVRY_ prefixed) + backward-compat aliases + 5 MCP Prompts.
8
+ * Exposes 2 tools (RVRY_deepthink, RVRY_problem_solve) + backward-compat aliases + 2 MCP Prompts.
9
9
  *
10
10
  * Also supports `npx @rvry/mcp setup` to install Claude Code commands.
11
11
  */
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * Thin client that proxies tool calls to the RVRY engine HTTP API.
6
6
  * Auth via RVRY_TOKEN env var (static rvry_ token).
7
7
  *
8
- * Exposes 5 tools (RVRY_ prefixed) + backward-compat aliases + 5 MCP Prompts.
8
+ * Exposes 2 tools (RVRY_deepthink, RVRY_problem_solve) + backward-compat aliases + 2 MCP Prompts.
9
9
  *
10
10
  * Also supports `npx @rvry/mcp setup` to install Claude Code commands.
11
11
  */
@@ -24,51 +24,18 @@ const questionCache = new Map();
24
24
  */
25
25
  const TOOL_MAP = {
26
26
  // Primary (rvry_ prefixed)
27
- RVRY_think: 'think',
28
27
  RVRY_deepthink: 'deepthink',
29
28
  RVRY_problem_solve: 'problem_solve',
30
- RVRY_challenge: 'challenge',
31
- RVRY_meta: 'meta',
32
29
  // Backward-compat aliases (unprefixed)
33
- think: 'think',
34
30
  deepthink: 'deepthink',
35
31
  problem_solve: 'problem_solve',
36
- challenge: 'challenge',
37
- meta: 'meta',
38
32
  };
39
33
  const TOOL_DEFS = [
40
- {
41
- name: 'RVRY_think',
42
- description: 'Carefully examine questions through guided rounds and self-checks. ' +
43
- 'Use for questions that need thorough examination but don\'t fit a specific pattern. ' +
44
- 'For high-stakes deep analysis, use RVRY_deepthink. ' +
45
- 'For choosing between options, use RVRY_problem_solve. ' +
46
- 'For stress-testing proposals, use RVRY_challenge. ' +
47
- 'For reflective examination of defaults and shifts, use RVRY_meta. ' +
48
- 'Call with your question to start, then send your analysis with the returned sessionId to continue.',
49
- inputSchema: {
50
- type: 'object',
51
- properties: {
52
- input: {
53
- type: 'string',
54
- description: 'The question to analyze (new session) or your analysis findings (continuation).',
55
- },
56
- sessionId: {
57
- type: 'string',
58
- description: 'Session ID for continuing an existing session. Omit to start a new session.',
59
- },
60
- skipScoping: {
61
- type: 'boolean',
62
- description: 'Skip the scoping questions phase and begin analysis immediately.',
63
- },
64
- },
65
- required: ['input'],
66
- },
67
- },
68
34
  {
69
35
  name: 'RVRY_deepthink',
70
- description: 'Extended analysis for high-stakes problems using pre-mortem failure analysis and structural constraints. ' +
71
- 'Use when RVRY_think reveals the question needs more depth. ' +
36
+ description: 'Deep, multi-round structured analysis for high-stakes or complex questions. ' +
37
+ 'Runs 5-7 rounds with rigorous self-checks and pre-mortem analysis. ' +
38
+ 'Use when the question demands thorough examination from multiple angles before reaching a conclusion. ' +
72
39
  'Call with your question to start, then send your analysis with the returned sessionId to continue.',
73
40
  inputSchema: {
74
41
  type: 'object',
@@ -113,54 +80,6 @@ const TOOL_DEFS = [
113
80
  required: ['input'],
114
81
  },
115
82
  },
116
- {
117
- name: 'RVRY_challenge',
118
- description: 'Adversarial evaluation of a proposal using causal analysis and edge-case auditing. ' +
119
- 'Returns a rigorous assessment of the proposal\'s viability. ' +
120
- 'Call with your proposal to start, then send your analysis with the returned sessionId to continue.',
121
- inputSchema: {
122
- type: 'object',
123
- properties: {
124
- input: {
125
- type: 'string',
126
- description: 'The proposal to challenge (new session) or your analysis findings (continuation).',
127
- },
128
- sessionId: {
129
- type: 'string',
130
- description: 'Session ID for continuing an existing session. Omit to start a new session.',
131
- },
132
- skipScoping: {
133
- type: 'boolean',
134
- description: 'Skip the scoping questions phase and begin analysis immediately.',
135
- },
136
- },
137
- required: ['input'],
138
- },
139
- },
140
- {
141
- name: 'RVRY_meta',
142
- description: 'Reflect on defaults, shifts, and reasoning posture across a session or topic. ' +
143
- 'Use to inspect what changed under reflection and what remained stable. ' +
144
- 'Call with your topic or session context to start, then send your reflection with the returned sessionId to continue.',
145
- inputSchema: {
146
- type: 'object',
147
- properties: {
148
- input: {
149
- type: 'string',
150
- description: 'The topic or session context to reflect on (new session) or your reflective findings (continuation).',
151
- },
152
- sessionId: {
153
- type: 'string',
154
- description: 'Session ID for continuing an existing session. Omit to start a new session.',
155
- },
156
- skipScoping: {
157
- type: 'boolean',
158
- description: 'Skip the scoping questions phase and begin analysis immediately.',
159
- },
160
- },
161
- required: ['input'],
162
- },
163
- },
164
83
  ];
165
84
  /** Strip response fields based on status for context-efficient MCP responses */
166
85
  function stripResponse(result, question) {
@@ -244,6 +163,18 @@ async function main() {
244
163
  if (result.status === 'complete') {
245
164
  questionCache.delete(result.sessionId);
246
165
  }
166
+ // Write local session summary on completion (non-blocking, silent failure)
167
+ if (result.status === 'complete' && result.harvest) {
168
+ import('./local-writer.js').then(({ writeLocalSession }) => {
169
+ writeLocalSession({
170
+ sessionId: result.sessionId,
171
+ operationType: rvryTool,
172
+ question,
173
+ rounds: result.round,
174
+ harvest: result.harvest,
175
+ }).catch(() => { });
176
+ }).catch(() => { });
177
+ }
247
178
  const stripped = stripResponse(result, question);
248
179
  return {
249
180
  content: [{ type: 'text', text: JSON.stringify(stripped) }],
@@ -265,36 +196,18 @@ async function main() {
265
196
  description: 'Deep structured analysis for high-stakes questions',
266
197
  arguments: [{ name: 'question', description: 'The question to analyze deeply', required: true }],
267
198
  },
268
- {
269
- name: 'RVRY_think',
270
- description: 'Structured analysis for questions needing careful examination',
271
- arguments: [{ name: 'question', description: 'The question to think through', required: true }],
272
- },
273
199
  {
274
200
  name: 'RVRY_problem_solve',
275
201
  description: 'Structured decision-making for problems with multiple options',
276
202
  arguments: [{ name: 'problem', description: 'The problem or decision to analyze', required: true }],
277
203
  },
278
- {
279
- name: 'RVRY_challenge',
280
- description: 'Adversarial evaluation of a proposal or plan',
281
- arguments: [{ name: 'proposal', description: 'The proposal or plan to challenge', required: true }],
282
- },
283
- {
284
- name: 'RVRY_meta',
285
- description: 'Reflective examination of defaults, shifts, and reasoning posture',
286
- arguments: [{ name: 'topic', description: 'The topic or session context to reflect on', required: true }],
287
- },
288
204
  ],
289
205
  }));
290
206
  server.setRequestHandler(GetPromptRequestSchema, async (request) => {
291
207
  const { name, arguments: args } = request.params;
292
208
  const promptMap = {
293
209
  RVRY_deepthink: `Use the RVRY_deepthink tool to analyze this question in depth: ${args?.question ?? ''}`,
294
- RVRY_think: `Use the RVRY_think tool to analyze this question: ${args?.question ?? ''}`,
295
210
  RVRY_problem_solve: `Use the RVRY_problem_solve tool to work through this decision: ${args?.problem ?? ''}`,
296
- RVRY_challenge: `Use the RVRY_challenge tool to stress-test this proposal: ${args?.proposal ?? ''}`,
297
- RVRY_meta: `Use the RVRY_meta tool to reflect on this topic or session context: ${args?.topic ?? ''}`,
298
211
  };
299
212
  const text = promptMap[name] ?? `Unknown prompt: ${name}`;
300
213
  return {
@@ -308,7 +221,7 @@ async function main() {
308
221
  });
309
222
  const transport = new StdioServerTransport();
310
223
  await server.connect(transport);
311
- console.error('[rvry-mcp] Connected via stdio. Tools: RVRY_think, RVRY_deepthink, RVRY_problem_solve, RVRY_challenge, RVRY_meta');
224
+ console.error('[rvry-mcp] Connected via stdio. Tools: RVRY_deepthink, RVRY_problem_solve');
312
225
  }
313
226
  // Export internals for testing
314
227
  export { TOOL_MAP, TOOL_DEFS, stripResponse, questionCache };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Local session writer -- writes a minimal markdown summary to .rvry/sessions/
3
+ * when a session completes.
4
+ *
5
+ * PROTOCOL SURFACE: This file produces user-facing output. It must NEVER contain
6
+ * constraint IDs, detection scores, mode names, gate verdicts, or any engine internals.
7
+ * Only question + harvest data.
8
+ */
9
+ interface LocalSessionData {
10
+ sessionId: string;
11
+ operationType: string;
12
+ question: string;
13
+ rounds: number;
14
+ harvest: {
15
+ summary: string;
16
+ keyFindings: string[];
17
+ openQuestions: string[];
18
+ followUps: Array<{
19
+ question: string;
20
+ rationale: string;
21
+ }>;
22
+ };
23
+ }
24
+ /**
25
+ * Write a minimal markdown summary of a completed session to .rvry/sessions/.
26
+ *
27
+ * Disabled via RVRY_LOCAL_SESSIONS=0. Fails silently on any error.
28
+ */
29
+ export declare function writeLocalSession(data: LocalSessionData): Promise<void>;
30
+ export {};
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Local session writer -- writes a minimal markdown summary to .rvry/sessions/
3
+ * when a session completes.
4
+ *
5
+ * PROTOCOL SURFACE: This file produces user-facing output. It must NEVER contain
6
+ * constraint IDs, detection scores, mode names, gate verdicts, or any engine internals.
7
+ * Only question + harvest data.
8
+ */
9
+ import { mkdirSync, writeFileSync } from 'fs';
10
+ import { join } from 'path';
11
+ /**
12
+ * Slugify a question string for use in filenames.
13
+ * Lowercases, replaces non-alphanumeric runs with hyphens, trims, and caps at maxLen chars.
14
+ */
15
+ function slugify(text, maxLen = 50) {
16
+ return text
17
+ .toLowerCase()
18
+ .replace(/[^a-z0-9]+/g, '-')
19
+ .replace(/^-+|-+$/g, '')
20
+ .slice(0, maxLen)
21
+ .replace(/-+$/, '');
22
+ }
23
+ /**
24
+ * Format a Date as YYYYMMDD-HHMM for filenames.
25
+ */
26
+ function formatTimestamp(date) {
27
+ const y = date.getFullYear().toString();
28
+ const mo = (date.getMonth() + 1).toString().padStart(2, '0');
29
+ const d = date.getDate().toString().padStart(2, '0');
30
+ const h = date.getHours().toString().padStart(2, '0');
31
+ const mi = date.getMinutes().toString().padStart(2, '0');
32
+ return `${y}${mo}${d}-${h}${mi}`;
33
+ }
34
+ /**
35
+ * Format a Date as YYYY-MM-DD HH:MM for YAML frontmatter.
36
+ */
37
+ function formatDate(date) {
38
+ const y = date.getFullYear().toString();
39
+ const mo = (date.getMonth() + 1).toString().padStart(2, '0');
40
+ const d = date.getDate().toString().padStart(2, '0');
41
+ const h = date.getHours().toString().padStart(2, '0');
42
+ const mi = date.getMinutes().toString().padStart(2, '0');
43
+ return `${y}-${mo}-${d} ${h}:${mi}`;
44
+ }
45
+ /**
46
+ * Build the markdown content for a completed session.
47
+ */
48
+ function buildMarkdown(data, now) {
49
+ const lines = [];
50
+ // YAML frontmatter
51
+ lines.push('---');
52
+ lines.push(`session: ${data.sessionId}`);
53
+ lines.push(`operation: ${data.operationType}`);
54
+ lines.push(`date: ${formatDate(now)}`);
55
+ lines.push(`rounds: ${data.rounds}`);
56
+ lines.push('---');
57
+ lines.push('');
58
+ // Question
59
+ lines.push(`# ${data.question}`);
60
+ lines.push('');
61
+ // Summary
62
+ lines.push('## Summary');
63
+ lines.push(data.harvest.summary);
64
+ lines.push('');
65
+ // Key Findings
66
+ if (data.harvest.keyFindings.length > 0) {
67
+ lines.push('## Key Findings');
68
+ for (const finding of data.harvest.keyFindings) {
69
+ lines.push(`- ${finding}`);
70
+ }
71
+ lines.push('');
72
+ }
73
+ // Open Questions
74
+ if (data.harvest.openQuestions.length > 0) {
75
+ lines.push('## Open Questions');
76
+ for (const q of data.harvest.openQuestions) {
77
+ lines.push(`- ${q}`);
78
+ }
79
+ lines.push('');
80
+ }
81
+ // Follow-ups
82
+ if (data.harvest.followUps.length > 0) {
83
+ lines.push('## Follow-ups');
84
+ for (const f of data.harvest.followUps) {
85
+ lines.push(`- ${f.question} — ${f.rationale}`);
86
+ }
87
+ lines.push('');
88
+ }
89
+ return lines.join('\n');
90
+ }
91
+ /**
92
+ * Write a minimal markdown summary of a completed session to .rvry/sessions/.
93
+ *
94
+ * Disabled via RVRY_LOCAL_SESSIONS=0. Fails silently on any error.
95
+ */
96
+ export async function writeLocalSession(data) {
97
+ // Check opt-out
98
+ if (process.env.RVRY_LOCAL_SESSIONS === '0')
99
+ return;
100
+ try {
101
+ const now = new Date();
102
+ const timestamp = formatTimestamp(now);
103
+ const slug = slugify(data.question);
104
+ const filename = `${timestamp}-${data.operationType}-${slug}.md`;
105
+ const sessionsDir = join(process.cwd(), '.rvry', 'sessions');
106
+ mkdirSync(sessionsDir, { recursive: true });
107
+ const filePath = join(sessionsDir, filename);
108
+ const content = buildMarkdown(data, now);
109
+ writeFileSync(filePath, content, 'utf-8');
110
+ }
111
+ catch {
112
+ // Fail silently -- this is a convenience feature, not critical path
113
+ }
114
+ }
package/dist/setup.js CHANGED
@@ -628,6 +628,32 @@ async function installCommands() {
628
628
  }
629
629
  return installed;
630
630
  }
631
+ // ── Gitignore protection ───────────────────────────────────────────
632
+ /**
633
+ * Ensure .rvry/ is in the project's .gitignore to prevent
634
+ * accidental commits of sensitive analysis content.
635
+ * Fails silently on any error.
636
+ */
637
+ function ensureGitignore() {
638
+ const gitignorePath = join(process.cwd(), '.gitignore');
639
+ try {
640
+ let content = '';
641
+ if (existsSync(gitignorePath)) {
642
+ content = readFileSync(gitignorePath, 'utf-8');
643
+ }
644
+ if (!content.includes('.rvry')) {
645
+ const newContent = content ? `${content.trimEnd()}\n.rvry/\n` : '.rvry/\n';
646
+ writeFileSync(gitignorePath, newContent, 'utf-8');
647
+ console.log(' Added .rvry/ to .gitignore');
648
+ }
649
+ else {
650
+ console.log(' .rvry/ already in .gitignore');
651
+ }
652
+ }
653
+ catch {
654
+ // Non-fatal -- read-only filesystem, permissions, etc.
655
+ }
656
+ }
631
657
  // ── Main setup flow ────────────────────────────────────────────────
632
658
  const BANNER = `
633
659
  8888888b. 888 888 8888888b. Y88b d88P
@@ -657,11 +683,11 @@ export async function runSetup() {
657
683
  process.exit(1);
658
684
  }
659
685
  token = flagValue;
660
- console.log('[1/4] Authentication (from --token flag)');
686
+ console.log('[1/5] Authentication (from --token flag)');
661
687
  console.log(` Token: ${maskToken(token)}`);
662
688
  }
663
689
  else {
664
- console.log('[1/4] Authentication');
690
+ console.log('[1/5] Authentication');
665
691
  console.log(' Opening browser for sign-in...');
666
692
  const result = await deviceAuthFlow();
667
693
  if (result) {
@@ -695,7 +721,7 @@ export async function runSetup() {
695
721
  }
696
722
  console.log('');
697
723
  // ── Step 2: Detect clients ──────────────────────────────────────
698
- console.log('[2/4] Detecting clients');
724
+ console.log('[2/5] Detecting clients');
699
725
  // Filter registry if --client was passed
700
726
  const clients = clientFilter
701
727
  ? CLIENT_REGISTRY.filter((c) => c.id === clientFilter)
@@ -715,7 +741,7 @@ export async function runSetup() {
715
741
  }
716
742
  console.log('');
717
743
  // ── Step 3: Configure clients ───────────────────────────────────
718
- console.log('[3/4] Select apps to add RVRY MCP configuration');
744
+ console.log('[3/5] Select apps to add RVRY MCP configuration');
719
745
  console.log(' Use \x1b[1m↑↓\x1b[0m to navigate, \x1b[1mspace\x1b[0m to toggle, \x1b[1ma\x1b[0m to toggle all, \x1b[1menter\x1b[0m to confirm');
720
746
  console.log('');
721
747
  const pickerItems = detected.map((d) => ({
@@ -764,7 +790,7 @@ export async function runSetup() {
764
790
  }
765
791
  console.log('');
766
792
  // ── Step 4: Slash commands ──────────────────────────────────────
767
- console.log('[4/4] Slash Commands');
793
+ console.log('[4/5] Slash Commands');
768
794
  const commandCount = await installCommands();
769
795
  if (commandCount > 0) {
770
796
  console.log(` Installed ${commandCount} command${commandCount > 1 ? 's' : ''} to .claude/commands/`);
@@ -773,6 +799,10 @@ export async function runSetup() {
773
799
  console.log(' No new commands installed (already up to date).');
774
800
  }
775
801
  console.log('');
802
+ // ── Step 5: Gitignore protection ────────────────────────────────
803
+ console.log('[5/5] Gitignore');
804
+ ensureGitignore();
805
+ console.log('');
776
806
  // ── Summary ─────────────────────────────────────────────────────
777
807
  console.log('─'.repeat(50));
778
808
  console.log('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rvry/mcp",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "RVRY reasoning depth enforcement (RDE) engine client.",
5
5
  "type": "module",
6
6
  "bin": {