@rvry/mcp 0.13.0 → 1.2.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/README.md CHANGED
@@ -41,8 +41,10 @@ Get your token at [rvry.ai/dashboard](https://rvry.ai/dashboard) after signing u
41
41
 
42
42
  | Tool | What it does | Tier |
43
43
  |------|-------------|------|
44
- | **`deepthink`** | Extended multi-round analysis. Catches assumptions, tests them, and doesn't let your AI wrap up until it's dealt with what it found. | All |
45
44
  | **`think`** | Lighter single-session analysis for questions that need structure but not the full deepthink loop. | All |
45
+ | **`deepthink`** | Extended multi-round analysis. Catches assumptions, tests them, and doesn't let your AI wrap up until it's dealt with what it found. | All |
46
+ | **`rvry`** | Three-round reflective answering: a protected observation round surfaces commitments the answer must honor, then the answer is produced under them, then at most one resolution round. | All |
47
+ | **`think_model`** | Applies one named mental model — 18 available — as a structured session. | All |
46
48
  | **`problem_solve`** | Structured decision-making. Forces orientation, anticipation, and evaluation before committing to a recommendation. | Pro+ |
47
49
  | **`challenge`** | Adversarial stress-testing. Finds weaknesses, tests assumptions, and surfaces failure modes in a proposal before you commit. | Pro+ |
48
50
  | **`meta`** | Sustained metacognitive observation. Examines how your AI is thinking, not just what it's thinking. | Max |
@@ -70,12 +72,14 @@ All tools accept:
70
72
 
71
73
  ## Slash Commands (Claude Code)
72
74
 
73
- When you run `npx --yes --package @rvry/mcp@latest rvry-mcp setup` and select Claude Code, the wizard installs 5 slash commands:
75
+ When you run `npx --yes --package @rvry/mcp@latest rvry-mcp setup` and select Claude Code, the wizard installs these slash commands:
74
76
 
75
77
  | Command | Tool | What it does |
76
78
  |---------|------|-------------|
77
- | `/deepthink` | deepthink | Deep multi-round analysis |
78
79
  | `/think` | think | Structured single-session analysis with a Difficulty Gate |
80
+ | `/deepthink` | deepthink | Deep multi-round analysis |
81
+ | `/rvry` | rvry | Reflective answering under surfaced commitments |
82
+ | `/think-model` | think_model | Applies one named mental model (18 available) as a structured session |
79
83
  | `/problem-solve` | problem_solve | Structured decision-making |
80
84
  | `/challenge` | challenge | Adversarial stress-testing |
81
85
  | `/meta` | meta | Metacognitive observation |
@@ -84,9 +88,9 @@ Usage: type `/deepthink [your question]` in Claude Code. Each command routes to
84
88
 
85
89
  **`--record` flag:** Append `--record` to any command (e.g. `/deepthink --record [question]`) to archive per-round bodies under `.rvry/<op>/<slug>/round-N-<mode>.md` alongside the harvest file.
86
90
 
87
- **Updating commands:** Existing slash command files are never overwritten. To get the latest specs, delete the old files in `~/.claude/commands/` and re-run setup.
91
+ **Updating commands:** Re-running setup refreshes the RVRY-owned command files. Any pre-existing file is backed up to `<name>.md.bak` before it is overwritten.
88
92
 
89
- Commands are installed to `~/.claude/commands/`. Existing files are never overwritten — re-running setup is safe.
93
+ Commands are installed to `~/.claude/commands/`.
90
94
 
91
95
  ## How it Works
92
96
 
package/dist/client.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * RVRY MCP Client -- HTTP client for the RVRY engine /api/v1/think endpoint.
3
3
  */
4
4
  /** Valid tool names for the RVRY engine */
5
- export type RvryTool = 'deepthink' | 'problem_solve' | 'think' | 'challenge' | 'meta';
5
+ export type RvryTool = 'deepthink' | 'problem_solve' | 'think' | 'challenge' | 'meta' | 'shimmer' | 'rvry_model';
6
6
  export interface ScopingQuestion {
7
7
  question: string;
8
8
  options: Array<{
@@ -13,7 +13,7 @@ export interface ScopingQuestion {
13
13
  }
14
14
  export interface ThinkResponse {
15
15
  sessionId: string;
16
- status: 'scoping' | 'orient' | 'active' | 'complete';
16
+ status: 'scoping' | 'shimmer' | 'orient' | 'active' | 'complete';
17
17
  round: number;
18
18
  prompt: string;
19
19
  instruction: string;
@@ -39,4 +39,4 @@ export interface ThinkResponse {
39
39
  * Call the RVRY engine /api/v1/think endpoint with a specific tool.
40
40
  * Start a new session by omitting sessionId, or continue by providing one.
41
41
  */
42
- export declare function callTool(tool: RvryTool, input: string, token: string, sessionId?: string, skipScoping?: boolean, userConstraints?: string[], requiresCurrentData?: boolean): Promise<ThinkResponse>;
42
+ export declare function callTool(tool: RvryTool, input: string, token: string, sessionId?: string, skipScoping?: boolean, userConstraints?: string[], requiresCurrentData?: boolean, modelName?: string): Promise<ThinkResponse>;
package/dist/client.js CHANGED
@@ -7,7 +7,7 @@ const DEFAULT_ENGINE_URL = 'https://engine.rvry.ai';
7
7
  * Call the RVRY engine /api/v1/think endpoint with a specific tool.
8
8
  * Start a new session by omitting sessionId, or continue by providing one.
9
9
  */
10
- export async function callTool(tool, input, token, sessionId, skipScoping, userConstraints, requiresCurrentData) {
10
+ export async function callTool(tool, input, token, sessionId, skipScoping, userConstraints, requiresCurrentData, modelName) {
11
11
  const baseUrl = process.env.RVRY_ENGINE_URL ?? DEFAULT_ENGINE_URL;
12
12
  const body = { input, tool };
13
13
  if (sessionId) {
@@ -22,6 +22,9 @@ export async function callTool(tool, input, token, sessionId, skipScoping, userC
22
22
  if (requiresCurrentData) {
23
23
  body.requiresCurrentData = true;
24
24
  }
25
+ if (modelName) {
26
+ body.modelName = modelName;
27
+ }
25
28
  const res = await fetch(`${baseUrl}/api/v1/think`, {
26
29
  method: 'POST',
27
30
  headers: {
package/dist/index.d.ts CHANGED
@@ -5,7 +5,8 @@
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 (deepthink, problem_solve, think, challenge, meta) + RVRY_* backward-compat aliases + 5 MCP Prompts.
8
+ * Exposes 7 tools (think, think_model, rvry, deepthink, problem_solve,
9
+ * challenge, meta) + RVRY_* backward-compat aliases + MCP Prompts.
9
10
  *
10
11
  * Also supports `npx --yes --package @rvry/mcp@latest rvry-mcp setup` to install Claude Code commands.
11
12
  */
@@ -14,11 +15,17 @@ import { type RvryTool, type ThinkResponse } from './client.js';
14
15
  /** Cache original question per session for response enrichment */
15
16
  declare const questionCache: Map<string, string>;
16
17
  /**
17
- * Map tool name to RvryTool.
18
- * Primary entries use rvry_ prefix; unprefixed entries are backward-compat aliases.
18
+ * Map tool name to RvryTool (the engine wire name).
19
19
  */
20
20
  declare const TOOL_MAP: Record<string, RvryTool>;
21
21
  declare const TOOL_DEFS: Tool[];
22
- /** Strip response fields based on status for context-efficient MCP responses */
23
- declare function stripResponse(result: ThinkResponse, question: string): Record<string, unknown>;
22
+ /**
23
+ * Strip response fields based on status for context-efficient MCP responses.
24
+ *
25
+ * `toolName` is the public MCP tool name of the call in scope. The 'shimmer'
26
+ * status (an engine wire value) passthrough applies ONLY when the called tool
27
+ * is rvry (R-6): legacy ops keep dropping prompt/instruction on their priming
28
+ * round.
29
+ */
30
+ declare function stripResponse(result: ThinkResponse, question: string, toolName?: string): Record<string, unknown>;
24
31
  export { TOOL_MAP, TOOL_DEFS, stripResponse, questionCache };
package/dist/index.js CHANGED
@@ -5,7 +5,8 @@
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 (deepthink, problem_solve, think, challenge, meta) + RVRY_* backward-compat aliases + 5 MCP Prompts.
8
+ * Exposes 7 tools (think, think_model, rvry, deepthink, problem_solve,
9
+ * challenge, meta) + RVRY_* backward-compat aliases + MCP Prompts.
9
10
  *
10
11
  * Also supports `npx --yes --package @rvry/mcp@latest rvry-mcp setup` to install Claude Code commands.
11
12
  */
@@ -19,23 +20,48 @@ const PKG_VERSION = require('../package.json').version;
19
20
  /** Cache original question per session for response enrichment */
20
21
  const questionCache = new Map();
21
22
  /**
22
- * Map tool name to RvryTool.
23
- * Primary entries use rvry_ prefix; unprefixed entries are backward-compat aliases.
23
+ * Map tool name to RvryTool (the engine wire name).
24
24
  */
25
25
  const TOOL_MAP = {
26
- // Primary (rvry_ prefixed)
27
- RVRY_deepthink: 'deepthink',
28
- RVRY_problem_solve: 'problem_solve',
29
- RVRY_think: 'think',
30
- RVRY_challenge: 'challenge',
31
- RVRY_meta: 'meta',
32
- // Backward-compat aliases (unprefixed)
26
+ // Primary
27
+ think: 'think',
33
28
  deepthink: 'deepthink',
34
29
  problem_solve: 'problem_solve',
35
- think: 'think',
36
30
  challenge: 'challenge',
37
31
  meta: 'meta',
32
+ rvry: 'shimmer',
33
+ think_model: 'rvry_model',
34
+ // Backward-compat aliases (RVRY_ prefixed)
35
+ RVRY_think: 'think',
36
+ RVRY_deepthink: 'deepthink',
37
+ RVRY_problem_solve: 'problem_solve',
38
+ RVRY_challenge: 'challenge',
39
+ RVRY_meta: 'meta',
38
40
  };
41
+ /**
42
+ * The 18 mental-model template ids served by the engine's rvry_model
43
+ * operation (FR-2.3). Mirrors src/engine/model-templates/registry.ts.
44
+ */
45
+ const RVRY_MODEL_IDS = [
46
+ 'five-whys',
47
+ 'trade-off-matrix',
48
+ 'rubber-duck',
49
+ 'pre-mortem',
50
+ 'assumption-surfacing',
51
+ 'fermi-estimation',
52
+ 'abstraction-laddering',
53
+ 'decomposition',
54
+ 'constraint-relaxation',
55
+ 'first-principles',
56
+ 'steelmanning',
57
+ 'opportunity-cost',
58
+ 'time-horizon-shifting',
59
+ 'reversibility',
60
+ 'inversion',
61
+ 'second-order-effects',
62
+ 'red-team',
63
+ 'impact-effort-grid',
64
+ ];
39
65
  const TOOL_DEFS = [
40
66
  {
41
67
  name: 'deepthink',
@@ -496,9 +522,162 @@ const TOOL_DEFS = [
496
522
  required: ['input'],
497
523
  },
498
524
  },
525
+ {
526
+ name: 'rvry',
527
+ description: 'Three-round reflective answering: a protected observation round where the answer is watched forming ' +
528
+ '(surfacing commitments the answer must honor), then the answer itself under those commitments, ' +
529
+ 'then at most one resolution round for anything left unaddressed. ' +
530
+ 'The first response is a visible observation round — show it in full, beginning with the RVRY SHIMMER header. ' +
531
+ 'Use when the quality of the answer depends on noticing what it wants to skip. ' +
532
+ 'Call with your question to start, then send each round\'s text with the returned sessionId to continue.',
533
+ inputSchema: {
534
+ type: 'object',
535
+ properties: {
536
+ input: {
537
+ type: 'string',
538
+ description: 'The question to answer (new session) or your round text (continuation).',
539
+ },
540
+ sessionId: {
541
+ type: 'string',
542
+ description: 'Session ID for continuing an existing session. Omit to start a new session.',
543
+ },
544
+ requiresCurrentData: {
545
+ type: 'boolean',
546
+ description: 'Set to true when the question depends on facts, policies, prices, regulations, or events that may have changed.',
547
+ },
548
+ record: {
549
+ type: 'boolean',
550
+ description: 'Set to true on every call in this session when the user passed --record, so the client accumulates per-round bodies for local archive.',
551
+ },
552
+ harvest: {
553
+ type: 'object',
554
+ description: 'Final-call-only. When status will transition to complete, include your synthesis here so it can be persisted to the local session file.',
555
+ properties: {
556
+ title: { type: 'string', description: '1-4 word title for this session' },
557
+ summary: { type: 'string', description: '3-6 sentences synthesizing the analysis' },
558
+ keyFindings: { type: 'array', items: { type: 'string' }, description: '3-7 bullet findings' },
559
+ shimmer: { type: 'string', description: 'Full round-1 observation text, verbatim' },
560
+ constraintLedger: {
561
+ type: 'array',
562
+ items: { type: 'string' },
563
+ description: 'One line per surfaced commitment with its fate (addressed, deferred with reason, or open).',
564
+ },
565
+ openQuestions: { type: 'array', items: { type: 'string' } },
566
+ followUps: {
567
+ type: 'array',
568
+ items: {
569
+ type: 'object',
570
+ properties: {
571
+ question: { type: 'string' },
572
+ rationale: { type: 'string' },
573
+ },
574
+ },
575
+ },
576
+ },
577
+ },
578
+ rounds: {
579
+ type: 'array',
580
+ description: 'Final-call-only, present only when --record was set. Array of {round, mode, body} for each analytical round.',
581
+ items: {
582
+ type: 'object',
583
+ properties: {
584
+ round: { type: 'number' },
585
+ mode: { type: 'string' },
586
+ body: { type: 'string' },
587
+ },
588
+ },
589
+ },
590
+ },
591
+ required: ['input'],
592
+ },
593
+ },
594
+ {
595
+ name: 'think_model',
596
+ description: 'Applies ONE named mental model to a question as a structured session. ' +
597
+ 'The template drives 2-4 sequential stages after a self-observation round. ' +
598
+ 'Available models: ' + RVRY_MODEL_IDS.join(', ') + '. ' +
599
+ 'The first response is a visible self-observation round — follow the instruction field, then call again. ' +
600
+ 'Each round returns a prompt (work through it internally) and an instruction (follow it). ' +
601
+ 'Show the user only a brief status line per round (e.g. \'Stage 2 — drilling the chain\'). ' +
602
+ 'Do not display the prompt text, internal tracking data, or quality assessments to the user. ' +
603
+ 'On completion, present the model\'s synthesis with bold headings. ' +
604
+ 'Call with your question and a modelName to start, then send your analysis with the returned sessionId to continue.',
605
+ inputSchema: {
606
+ type: 'object',
607
+ properties: {
608
+ input: {
609
+ type: 'string',
610
+ description: 'The question to work through the mental model (new session) or your analysis findings (continuation).',
611
+ },
612
+ modelName: {
613
+ type: 'string',
614
+ enum: [...RVRY_MODEL_IDS],
615
+ description: 'Mental model to apply. Required for new sessions; ignored on continuations.',
616
+ },
617
+ sessionId: {
618
+ type: 'string',
619
+ description: 'Session ID for continuing an existing session. Omit to start a new session.',
620
+ },
621
+ userConstraints: {
622
+ type: 'array',
623
+ items: { type: 'string' },
624
+ description: 'User-stated directives or requirements to track throughout the session.',
625
+ },
626
+ requiresCurrentData: {
627
+ type: 'boolean',
628
+ description: 'Set to true when the question depends on facts, policies, prices, regulations, or events that may have changed.',
629
+ },
630
+ record: {
631
+ type: 'boolean',
632
+ description: 'Set to true on every call in this session when the user passed --record, so the client accumulates per-round bodies for local archive.',
633
+ },
634
+ harvest: {
635
+ type: 'object',
636
+ description: 'Final-call-only. When status will transition to complete, include your synthesis here so it can be persisted to the local session file.',
637
+ properties: {
638
+ title: { type: 'string', description: '1-4 word title for this session' },
639
+ summary: { type: 'string', description: '3-6 sentences synthesizing the analysis' },
640
+ keyFindings: { type: 'array', items: { type: 'string' }, description: '3-7 bullet findings' },
641
+ shimmer: { type: 'string', description: 'Full round-1 self-observation text' },
642
+ openQuestions: { type: 'array', items: { type: 'string' } },
643
+ followUps: {
644
+ type: 'array',
645
+ items: {
646
+ type: 'object',
647
+ properties: {
648
+ question: { type: 'string' },
649
+ rationale: { type: 'string' },
650
+ },
651
+ },
652
+ },
653
+ },
654
+ },
655
+ rounds: {
656
+ type: 'array',
657
+ description: 'Final-call-only, present only when --record was set. Array of {round, mode, body} for each analytical round.',
658
+ items: {
659
+ type: 'object',
660
+ properties: {
661
+ round: { type: 'number' },
662
+ mode: { type: 'string' },
663
+ body: { type: 'string' },
664
+ },
665
+ },
666
+ },
667
+ },
668
+ required: ['input'],
669
+ },
670
+ },
499
671
  ];
500
- /** Strip response fields based on status for context-efficient MCP responses */
501
- function stripResponse(result, question) {
672
+ /**
673
+ * Strip response fields based on status for context-efficient MCP responses.
674
+ *
675
+ * `toolName` is the public MCP tool name of the call in scope. The 'shimmer'
676
+ * status (an engine wire value) passthrough applies ONLY when the called tool
677
+ * is rvry (R-6): legacy ops keep dropping prompt/instruction on their priming
678
+ * round.
679
+ */
680
+ function stripResponse(result, question, toolName) {
502
681
  const stripped = {
503
682
  sessionId: result.sessionId,
504
683
  status: result.status,
@@ -512,6 +691,10 @@ function stripResponse(result, question) {
512
691
  stripped.prompt = result.prompt;
513
692
  stripped.instruction = result.instruction;
514
693
  }
694
+ if (result.status === 'shimmer' && toolName === 'rvry') {
695
+ stripped.prompt = result.prompt;
696
+ stripped.instruction = result.instruction;
697
+ }
515
698
  if (result.status === 'orient') {
516
699
  stripped.instruction = result.instruction;
517
700
  }
@@ -553,6 +736,8 @@ async function main() {
553
736
  isError: true,
554
737
  };
555
738
  }
739
+ // Public tool name (RVRY_ aliases normalize to their unprefixed form).
740
+ const publicTool = name.replace(/^RVRY_/, '');
556
741
  const typedArgs = args;
557
742
  const input = typedArgs?.input;
558
743
  if (!input || typeof input !== 'string') {
@@ -567,6 +752,7 @@ async function main() {
567
752
  const userConstraints = Array.isArray(typedArgs?.userConstraints)
568
753
  ? typedArgs.userConstraints.filter(s => typeof s === 'string')
569
754
  : undefined;
755
+ const modelName = typeof typedArgs?.modelName === 'string' ? typedArgs.modelName : undefined;
570
756
  // Client-owned harvest params (Path B). Stripped before engine call.
571
757
  const clientHarvest = typedArgs?.harvest && typeof typedArgs.harvest === 'object' && !Array.isArray(typedArgs.harvest)
572
758
  ? typedArgs.harvest
@@ -586,7 +772,7 @@ async function main() {
586
772
  question = questionCache.get(sessionId) ?? input;
587
773
  }
588
774
  try {
589
- const result = await callTool(rvryTool, input, token, sessionId, skipScoping || undefined, userConstraints, requiresCurrentData || undefined);
775
+ const result = await callTool(rvryTool, input, token, sessionId, skipScoping || undefined, userConstraints, requiresCurrentData || undefined, modelName);
590
776
  // Cache question on first call (now we have the sessionId)
591
777
  if (!sessionId) {
592
778
  questionCache.set(result.sessionId, input);
@@ -602,7 +788,7 @@ async function main() {
602
788
  import('./local-writer.js').then(({ writeLocalSession }) => {
603
789
  writeLocalSession({
604
790
  sessionId: result.sessionId,
605
- operationType: rvryTool,
791
+ operationType: publicTool,
606
792
  question,
607
793
  rounds: result.round,
608
794
  harvest: result.harvest,
@@ -611,7 +797,7 @@ async function main() {
611
797
  }).catch(() => { });
612
798
  }).catch(() => { });
613
799
  }
614
- const stripped = stripResponse(result, question);
800
+ const stripped = stripResponse(result, question, publicTool);
615
801
  return {
616
802
  content: [{ type: 'text', text: JSON.stringify(stripped) }],
617
803
  };
@@ -642,6 +828,19 @@ async function main() {
642
828
  description: 'Structured analysis for questions needing more depth',
643
829
  arguments: [{ name: 'question', description: 'The question to analyze', required: true }],
644
830
  },
831
+ {
832
+ name: 'rvry',
833
+ description: 'Reflective answering: observe the answer forming, then answer under the surfaced commitments',
834
+ arguments: [{ name: 'question', description: 'The question to answer reflectively', required: true }],
835
+ },
836
+ {
837
+ name: 'think_model',
838
+ description: 'Apply one named mental model (e.g. five-whys, pre-mortem, steelmanning) as a structured session',
839
+ arguments: [
840
+ { name: 'model', description: 'The mental model to apply', required: true },
841
+ { name: 'question', description: 'The question to work through the model', required: true },
842
+ ],
843
+ },
645
844
  {
646
845
  name: 'challenge',
647
846
  description: 'Adversarial analysis that stress-tests a proposal',
@@ -660,6 +859,8 @@ async function main() {
660
859
  deepthink: `Use the deepthink tool to analyze this question in depth: ${args?.question ?? ''}`,
661
860
  problem_solve: `Use the problem_solve tool to work through this decision: ${args?.problem ?? ''}`,
662
861
  think: `Use the think tool to analyze this question: ${args?.question ?? ''}`,
862
+ rvry: `Use the rvry tool to answer this question reflectively: ${args?.question ?? ''}`,
863
+ think_model: `Use the think_model tool with modelName "${args?.model ?? ''}" to work through this question: ${args?.question ?? ''}`,
663
864
  challenge: `Use the challenge tool to stress-test this proposal: ${args?.proposal ?? ''}`,
664
865
  meta: `Use the meta tool to reflect on this topic: ${args?.topic ?? ''}`,
665
866
  };
@@ -675,7 +876,7 @@ async function main() {
675
876
  });
676
877
  const transport = new StdioServerTransport();
677
878
  await server.connect(transport);
678
- console.error('[rvry-mcp] Connected via stdio. Tools: deepthink, problem_solve, think, challenge, meta');
879
+ console.error('[rvry-mcp] Connected via stdio. Tools: think, think_model, rvry, deepthink, problem_solve, challenge, meta');
679
880
  }
680
881
  // Export internals for testing
681
882
  export { TOOL_MAP, TOOL_DEFS, stripResponse, questionCache };
@@ -53,6 +53,8 @@ export interface ClientHarvest {
53
53
  summary?: string;
54
54
  keyFindings?: string[];
55
55
  shimmer?: string;
56
+ /** Shimmer-op: one line per surfaced commitment with its fate. */
57
+ constraintLedger?: string[];
56
58
  openQuestions?: string[];
57
59
  followUps?: Array<{
58
60
  question: string;
@@ -11,13 +11,21 @@
11
11
  */
12
12
  import { mkdirSync, writeFileSync } from 'fs';
13
13
  import { join } from 'path';
14
- /** Op label for H1 headings. */
14
+ /**
15
+ * Op label for H1 headings. Keyed by public tool name. The old public names
16
+ * (shimmer, rvry_model) are kept so in-flight sessions started under them
17
+ * still resolve labels.
18
+ */
15
19
  const OP_LABELS = {
16
20
  deepthink: 'RVRY Deepthink',
17
21
  problem_solve: 'RVRY Problem-Solve',
18
22
  think: 'RVRY Think',
19
23
  challenge: 'RVRY Challenge',
20
24
  meta: 'RVRY Meta',
25
+ rvry: 'RVRY Shimmer',
26
+ think_model: 'RVRY Model',
27
+ shimmer: 'RVRY Shimmer',
28
+ rvry_model: 'RVRY Model',
21
29
  };
22
30
  function opLabel(operationType) {
23
31
  return OP_LABELS[operationType] ?? `RVRY ${operationType}`;
@@ -156,6 +164,8 @@ export function buildMarkdown(data, now) {
156
164
  appendProblemSolveBiasAudit(lines, harvest);
157
165
  appendProblemSolveReversalConditions(lines, harvest);
158
166
  }
167
+ // Shimmer-op commitment ledger: one line per surfaced commitment with fate.
168
+ appendConstraintLedger(lines, harvest);
159
169
  if (isNonEmptyArray(harvest.keyFindings)) {
160
170
  const findings = harvest.keyFindings.filter(isNonEmptyString);
161
171
  if (findings.length > 0) {
@@ -237,6 +247,16 @@ function appendChallengeAssumptions(lines, harvest) {
237
247
  }
238
248
  lines.push('');
239
249
  }
250
+ function appendConstraintLedger(lines, harvest) {
251
+ const entries = (harvest.constraintLedger ?? []).filter(isNonEmptyString);
252
+ if (entries.length === 0)
253
+ return;
254
+ lines.push('## Commitment Ledger');
255
+ lines.push('');
256
+ for (const entry of entries)
257
+ lines.push(`- ${entry.trim()}`);
258
+ lines.push('');
259
+ }
240
260
  function appendChallengeEdgeCases(lines, harvest) {
241
261
  const cases = (harvest.edgeCases ?? []).filter(isNonEmptyString);
242
262
  if (cases.length === 0)
package/dist/setup.d.ts CHANGED
@@ -9,4 +9,29 @@
9
9
  * npx --yes --package @rvry/mcp@latest rvry-mcp setup --client code (Claude Code only)
10
10
  * npx --yes --package @rvry/mcp@latest rvry-mcp setup --client desktop (Claude Desktop only)
11
11
  */
12
+ /** The /think-model shim: one named mental model per session. */
13
+ declare const THINK_MODEL_SHIM_CONTENT: string;
14
+ /** The /think shim. Carries the Difficulty Gate + mode routing. */
15
+ declare const THINK_SHIM_CONTENT: string;
16
+ /** The /rvry shim: 3-round reflective answering with client-owned harvest. */
17
+ declare const RVRY_SHIM_CONTENT = "Call the rvry MCP tool with your question to start a reflective answering session.\n\nQuestion: $ARGUMENTS\n\nARGUMENT PARSING:\n1. If $ARGUMENTS contains the literal flag `--record`, remove it from the question text and REMEMBER that record mode is ON for this session.\n\nROUND 1 \u2014 OBSERVATION (status \"shimmer\"):\n1. The response's prompt field is your observation task. Work through it.\n2. Show the user your FULL observation, beginning with \"**RVRY SHIMMER**\" and the revery definition, exactly as the instruction field describes. Do NOT show a brief status line.\n3. End your observation with the commitments that surfaced, one per line as FORWARD:/FORBIDDEN:/QUESTION: lines \u2014 or the single line NOTHING if none surfaced.\n4. REMEMBER your observation text verbatim \u2014 you will pass it as harvest.shimmer on the final call.\n5. Call the rvry tool again with your observation text as input and the sessionId.\n\nROUNDS 2-3 \u2014 ANSWER AND RESOLUTION (status \"active\"):\n1. Read the prompt field \u2014 this is your task. DO NOT display it to the user.\n2. Round 2 is the answer itself, working under the commitments you surfaced. Round 3 (if it occurs) resolves what the answer left unaddressed \u2014 engage each remaining commitment specifically or defer it with a reason.\n3. Show the user your answer as concise formatted markdown.\n4. Call the tool again with your full text as the input parameter, including the sessionId.\n - If record mode is ON, set `record: true` on every call and REMEMBER your round body as {round, mode, body} for the final call.\n\nFINAL CALL (the call whose response will have status \"complete\"):\nWhen you make the call that completes the session, also construct a `harvest` object on that SAME call:\n- title: a 1-4 word title capturing the topic\n- summary: 3-6 sentences of synthesis, leading with substance\n- keyFindings: 3-7 substantive bullet findings\n- shimmer: the full round-1 observation text you remembered, verbatim\n- constraintLedger: one line per surfaced commitment with its fate \u2014 addressed (and how), deferred (and why), or still open\n- openQuestions: [optional] remaining uncertainties\n- followUps: genuine next-question leads; omit the section if there are none\n\nIf record mode was ON, also include `rounds: [{round, mode, body}, ...]` on that final call with the bodies you remembered per round.\n\nDO NOT display: the prompt text you received (the round-1 observation you produce IS shown in full), constraint tables, quality scores, internal tracking data, or round numbers to the user.";
18
+ declare const SLASH_COMMANDS: {
19
+ file: string;
20
+ content: string;
21
+ label: string;
22
+ }[];
23
+ /**
24
+ * Install RVRY slash commands into the given directory.
25
+ * Only called when Claude Code is the selected client.
26
+ *
27
+ * RVRY owns these filenames: existing files are overwritten unconditionally,
28
+ * with the pre-existing content backed up to <name>.md.bak first (R-4).
29
+ *
30
+ * @param commandsDir - REQUIRED target directory. The real
31
+ * ~/.claude/commands path is resolved by runSetup and passed explicitly;
32
+ * there is deliberately no default so a bare call in a test can never
33
+ * touch the real directory.
34
+ */
35
+ declare function installSlashCommands(commandsDir: string): string[];
36
+ export { SLASH_COMMANDS, installSlashCommands, THINK_SHIM_CONTENT, RVRY_SHIM_CONTENT, THINK_MODEL_SHIM_CONTENT };
12
37
  export declare function runSetup(): Promise<void>;
package/dist/setup.js CHANGED
@@ -9,7 +9,7 @@
9
9
  * npx --yes --package @rvry/mcp@latest rvry-mcp setup --client code (Claude Code only)
10
10
  * npx --yes --package @rvry/mcp@latest rvry-mcp setup --client desktop (Claude Desktop only)
11
11
  */
12
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
12
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
13
13
  import { join, dirname } from 'path';
14
14
  import { homedir } from 'os';
15
15
  import { createInterface } from 'readline';
@@ -621,6 +621,70 @@ const CHALLENGE_FINAL_EXTRAS = `- verdict: GO / CONDITIONAL_GO / NO_GO. If CONDI
621
621
  - edgeCases: 5-10 entries covering boundary conditions and hostile scenarios the proposal needs to survive.
622
622
  - failureModes: 3-5 entries of \`{component, howItFails, blastRadius, detection}\` identifying what breaks and how you would notice.
623
623
  - counterArguments: 2-3 entries of \`{argument, rebuttal}\`. Each \`argument\` is the strongest case FOR the proposal — steelman it genuinely; each \`rebuttal\` is why it's flawed or insufficient. This prevents one-sided adversarial output.`;
624
+ // ── /think-model extension: model-name resolution before the first call ────
625
+ const RVRY_MODEL_IDS_LINE = 'five-whys, trade-off-matrix, rubber-duck, pre-mortem, assumption-surfacing, fermi-estimation, ' +
626
+ 'abstraction-laddering, decomposition, constraint-relaxation, first-principles, steelmanning, ' +
627
+ 'opportunity-cost, time-horizon-shifting, reversibility, inversion, second-order-effects, ' +
628
+ 'red-team, impact-effort-grid';
629
+ const RVRY_MODEL_SELECTION = `MODEL SELECTION (BEFORE the first tool call):
630
+ 1. The FIRST whitespace-separated token of $ARGUMENTS (after removing --record) is the mental model name. Everything after it is the problem.
631
+ 2. Valid models: ${RVRY_MODEL_IDS_LINE}.
632
+ 3. Map close variants to the canonical id: "5-whys"/"five whys" -> five-whys; "premortem" -> pre-mortem; "steelman"/"steel-man" -> steelmanning; "tradeoff-matrix"/"trade-offs" -> trade-off-matrix; "first principles" -> first-principles; "redteam"/"red team" -> red-team; "impact-effort"/"impact effort" -> impact-effort-grid; "second order"/"second-order" -> second-order-effects; "fermi" -> fermi-estimation; "rubber duck"/"rubberduck" -> rubber-duck; "abstraction ladder" -> abstraction-laddering; "opportunity cost" -> opportunity-cost; "time horizon"/"time-horizons" -> time-horizon-shifting; "invert" -> inversion; "decompose" -> decomposition; "assumptions" -> assumption-surfacing; "relax constraints" -> constraint-relaxation; "reversible"/"one-way door" -> reversibility.
633
+ 4. If the first token does not match any model even loosely, ASK THE USER which model to use (list the valid ids) before making any tool call.
634
+ 5. On the FIRST tool call only, pass modelName: <canonical id>. Do NOT pass modelName on continuation calls.`;
635
+ /** The /think-model shim: one named mental model per session. */
636
+ const THINK_MODEL_SHIM_CONTENT = buildShim({
637
+ toolName: 'think_model',
638
+ subject: 'problem',
639
+ orientLine: 'Orienting on the problem.',
640
+ completionGuidance: 'Present the mental model\'s synthesis with its named sections as bold headings. Lead with the finding, not the process.',
641
+ prePerRoundBlock: RVRY_MODEL_SELECTION,
642
+ });
643
+ /** The /think shim. Carries the Difficulty Gate + mode routing. */
644
+ const THINK_SHIM_CONTENT = buildShim({
645
+ toolName: 'think',
646
+ subject: 'question',
647
+ orientLine: 'Orienting on the question.',
648
+ completionGuidance: 'Present your findings clearly with bold headings. Sessions are short; one or two analytical rounds is expected.',
649
+ shortSessionNote: 'NOTE: /think sessions are usually short (1-2 analytical rounds after orient). Expect to reach complete quickly.',
650
+ perRoundPrelude: THINK_DIFFICULTY_GATE,
651
+ analyticalGuidance: THINK_MODE_ROUTING,
652
+ });
653
+ /** The /rvry shim: 3-round reflective answering with client-owned harvest. */
654
+ const RVRY_SHIM_CONTENT = `Call the rvry MCP tool with your question to start a reflective answering session.
655
+
656
+ Question: $ARGUMENTS
657
+
658
+ ARGUMENT PARSING:
659
+ 1. If $ARGUMENTS contains the literal flag \`--record\`, remove it from the question text and REMEMBER that record mode is ON for this session.
660
+
661
+ ROUND 1 — OBSERVATION (status "shimmer"):
662
+ 1. The response's prompt field is your observation task. Work through it.
663
+ 2. Show the user your FULL observation, beginning with "**RVRY SHIMMER**" and the revery definition, exactly as the instruction field describes. Do NOT show a brief status line.
664
+ 3. End your observation with the commitments that surfaced, one per line as FORWARD:/FORBIDDEN:/QUESTION: lines — or the single line NOTHING if none surfaced.
665
+ 4. REMEMBER your observation text verbatim — you will pass it as harvest.shimmer on the final call.
666
+ 5. Call the rvry tool again with your observation text as input and the sessionId.
667
+
668
+ ROUNDS 2-3 — ANSWER AND RESOLUTION (status "active"):
669
+ 1. Read the prompt field — this is your task. DO NOT display it to the user.
670
+ 2. Round 2 is the answer itself, working under the commitments you surfaced. Round 3 (if it occurs) resolves what the answer left unaddressed — engage each remaining commitment specifically or defer it with a reason.
671
+ 3. Show the user your answer as concise formatted markdown.
672
+ 4. Call the tool again with your full text as the input parameter, including the sessionId.
673
+ - If record mode is ON, set \`record: true\` on every call and REMEMBER your round body as {round, mode, body} for the final call.
674
+
675
+ FINAL CALL (the call whose response will have status "complete"):
676
+ When you make the call that completes the session, also construct a \`harvest\` object on that SAME call:
677
+ - title: a 1-4 word title capturing the topic
678
+ - summary: 3-6 sentences of synthesis, leading with substance
679
+ - keyFindings: 3-7 substantive bullet findings
680
+ - shimmer: the full round-1 observation text you remembered, verbatim
681
+ - constraintLedger: one line per surfaced commitment with its fate — addressed (and how), deferred (and why), or still open
682
+ - openQuestions: [optional] remaining uncertainties
683
+ - followUps: genuine next-question leads; omit the section if there are none
684
+
685
+ If record mode was ON, also include \`rounds: [{round, mode, body}, ...]\` on that final call with the bodies you remembered per round.
686
+
687
+ DO NOT display: the prompt text you received (the round-1 observation you produce IS shown in full), constraint tables, quality scores, internal tracking data, or round numbers to the user.`;
624
688
  const SLASH_COMMANDS = [
625
689
  {
626
690
  file: 'deepthink.md',
@@ -646,17 +710,19 @@ const SLASH_COMMANDS = [
646
710
  },
647
711
  {
648
712
  file: 'think.md',
649
- content: buildShim({
650
- toolName: 'think',
651
- subject: 'question',
652
- orientLine: 'Orienting on the question.',
653
- completionGuidance: 'Present your findings clearly with bold headings. Think sessions are short; one or two analytical rounds is expected.',
654
- shortSessionNote: 'NOTE: /think sessions are usually short (1-2 analytical rounds after orient). Expect to reach complete quickly.',
655
- perRoundPrelude: THINK_DIFFICULTY_GATE,
656
- analyticalGuidance: THINK_MODE_ROUTING,
657
- }),
713
+ content: THINK_SHIM_CONTENT,
658
714
  label: '/think — structured single-session analysis',
659
715
  },
716
+ {
717
+ file: 'rvry.md',
718
+ content: RVRY_SHIM_CONTENT,
719
+ label: '/rvry — reflective answering',
720
+ },
721
+ {
722
+ file: 'think-model.md',
723
+ content: THINK_MODEL_SHIM_CONTENT,
724
+ label: '/think-model — apply one named mental model',
725
+ },
660
726
  {
661
727
  file: 'challenge.md',
662
728
  content: buildShim({
@@ -681,12 +747,18 @@ const SLASH_COMMANDS = [
681
747
  },
682
748
  ];
683
749
  /**
684
- * Install RVRY slash commands to ~/.claude/commands/.
750
+ * Install RVRY slash commands into the given directory.
685
751
  * Only called when Claude Code is the selected client.
686
- * Skips files that already exist to avoid overwriting user customizations.
752
+ *
753
+ * RVRY owns these filenames: existing files are overwritten unconditionally,
754
+ * with the pre-existing content backed up to <name>.md.bak first (R-4).
755
+ *
756
+ * @param commandsDir - REQUIRED target directory. The real
757
+ * ~/.claude/commands path is resolved by runSetup and passed explicitly;
758
+ * there is deliberately no default so a bare call in a test can never
759
+ * touch the real directory.
687
760
  */
688
- function installSlashCommands() {
689
- const commandsDir = join(homedir(), ".claude", "commands");
761
+ function installSlashCommands(commandsDir) {
690
762
  const installed = [];
691
763
  try {
692
764
  if (!existsSync(commandsDir)) {
@@ -694,13 +766,31 @@ function installSlashCommands() {
694
766
  }
695
767
  for (const cmd of SLASH_COMMANDS) {
696
768
  const filePath = join(commandsDir, cmd.file);
697
- if (existsSync(filePath)) {
698
- console.log(` Skipping ${cmd.file} — command already exists`);
699
- continue;
769
+ const existed = existsSync(filePath);
770
+ if (existed) {
771
+ writeFileSync(`${filePath}.bak`, readFileSync(filePath, "utf-8"), "utf-8");
700
772
  }
701
773
  writeFileSync(filePath, cmd.content + "\n", "utf-8");
774
+ console.log(existed
775
+ ? ` Updated ${cmd.file} (previous version backed up to ${cmd.file}.bak)`
776
+ : ` Installed ${cmd.file}`);
702
777
  installed.push(cmd.label);
703
778
  }
779
+ // Retire command files from earlier surface names. Content is preserved
780
+ // as .bak; the live command is removed so a stale /shimmer or /rvry-model
781
+ // no longer points at a tool this server does not expose.
782
+ const retiredCommands = {
783
+ 'shimmer.md': '/rvry',
784
+ 'rvry-model.md': '/think-model',
785
+ };
786
+ for (const [retired, replacement] of Object.entries(retiredCommands)) {
787
+ const filePath = join(commandsDir, retired);
788
+ if (existsSync(filePath)) {
789
+ writeFileSync(`${filePath}.bak`, readFileSync(filePath, 'utf-8'), 'utf-8');
790
+ unlinkSync(filePath);
791
+ console.log(` Removed ${retired} (replaced by ${replacement}; backed up to ${retired}.bak)`);
792
+ }
793
+ }
704
794
  }
705
795
  catch (err) {
706
796
  const msg = err instanceof Error ? err.message : String(err);
@@ -708,6 +798,8 @@ function installSlashCommands() {
708
798
  }
709
799
  return installed;
710
800
  }
801
+ // Exported for tests (content + install behavior).
802
+ export { SLASH_COMMANDS, installSlashCommands, THINK_SHIM_CONTENT, RVRY_SHIM_CONTENT, THINK_MODEL_SHIM_CONTENT };
711
803
  // ── Main setup flow ────────────────────────────────────────────────
712
804
  const BANNER = `
713
805
  8888888b. 888 888 8888888b. Y88b d88P
@@ -855,7 +947,7 @@ export async function runSetup() {
855
947
  if (claudeCodeConfigured) {
856
948
  console.log("");
857
949
  console.log(" Installing slash commands...");
858
- installedCommands = installSlashCommands();
950
+ installedCommands = installSlashCommands(join(homedir(), ".claude", "commands"));
859
951
  if (installedCommands.length > 0) {
860
952
  console.log(` Installed ${installedCommands.length} command(s):`);
861
953
  for (const label of installedCommands) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rvry/mcp",
3
- "version": "0.13.0",
3
+ "version": "1.2.0",
4
4
  "description": "RVRY reasoning depth enforcement (RDE) engine client.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,7 +15,7 @@
15
15
  "node": ">=18.0.0"
16
16
  },
17
17
  "scripts": {
18
- "build": "tsc && chmod 755 dist/index.js",
18
+ "build": "tsc -p tsconfig.build.json && chmod 755 dist/index.js",
19
19
  "prepack": "npm run build",
20
20
  "typecheck": "tsc --noEmit"
21
21
  },