projecta-rrr 1.15.2 → 1.15.3

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/bin/install.js CHANGED
@@ -736,6 +736,41 @@ function install(isGlobal) {
736
736
  copyWithPathReplacement(skillSrc, skillDest, pathPrefix);
737
737
  console.log(` ${green}✓${reset} Installed rrr`);
738
738
 
739
+ // Install RRR runtime dependencies (tree-sitter, lancedb, etc.)
740
+ // These are needed by lib/search/ modules which the MCP server imports
741
+ if (fs.existsSync(path.join(skillDest, 'package.json'))) {
742
+ try {
743
+ console.log(` ${dim}Installing RRR runtime dependencies (may take a moment)...${reset}`);
744
+ execSync('npm install --omit=dev --silent 2>/dev/null || npm install --omit=dev', {
745
+ cwd: skillDest,
746
+ stdio: 'pipe',
747
+ timeout: 120000 // 2 min for native modules
748
+ });
749
+ console.log(` ${green}✓${reset} Installed RRR runtime dependencies`);
750
+ } catch (e) {
751
+ console.log(` ${yellow}⚠${reset} RRR runtime deps failed: ${e.message}`);
752
+ console.log(` ${dim}Run manually: cd ${skillDest} && npm install${reset}`);
753
+ }
754
+ }
755
+
756
+ // Install MCP server dependencies
757
+ // The mcp-server needs @modelcontextprotocol/sdk which isn't bundled in npm package
758
+ const mcpServerDir = path.join(skillDest, 'mcp-server');
759
+ if (fs.existsSync(path.join(mcpServerDir, 'package.json'))) {
760
+ try {
761
+ console.log(` ${dim}Installing MCP server dependencies...${reset}`);
762
+ execSync('npm install --omit=dev --silent 2>/dev/null || npm install --omit=dev', {
763
+ cwd: mcpServerDir,
764
+ stdio: 'pipe',
765
+ timeout: 60000
766
+ });
767
+ console.log(` ${green}✓${reset} Installed MCP server dependencies`);
768
+ } catch (e) {
769
+ console.log(` ${yellow}⚠${reset} MCP server deps failed: ${e.message}`);
770
+ console.log(` ${dim}Run manually: cd ${mcpServerDir} && npm install${reset}`);
771
+ }
772
+ }
773
+
739
774
  // Copy agents to ~/.claude/agents (subagents must be at root level)
740
775
  const agentsSrc = path.join(src, 'agents');
741
776
  if (fs.existsSync(agentsSrc)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projecta-rrr",
3
- "version": "1.15.2",
3
+ "version": "1.15.3",
4
4
  "description": "A meta-prompting, context engineering and spec-driven development system for Claude Code by Projecta.ai",
5
5
  "bin": {
6
6
  "projecta-rrr": "bin/install.js"
@@ -32,6 +32,7 @@
32
32
  "scripts",
33
33
  "mcp.registry.json",
34
34
  "projecta.defaults.json",
35
+ "rrr/package.json",
35
36
  "rrr/lib",
36
37
  "rrr/mcp-server",
37
38
  "rrr/presets",
@@ -19,8 +19,9 @@ const fs = require('fs');
19
19
  const path = require('path');
20
20
  const os = require('os');
21
21
 
22
- // Settings file path
22
+ // Settings file paths - Claude Code reads MCPs from multiple locations
23
23
  const SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
24
+ const ALT_SETTINGS_PATH = path.join(os.homedir(), '.claude.json');
24
25
 
25
26
  // LanceDB storage constants
26
27
  const STORAGE_DIR = '.rrr/search';
@@ -50,109 +51,181 @@ const OLD_INDEX_DIRS = [
50
51
  ];
51
52
 
52
53
  /**
53
- * Detect conflicting search MCPs in settings.
54
+ * Check a settings object for conflicting MCPs.
54
55
  *
55
- * @returns {{found: string[], settings: Object}} Found conflicting IDs and settings object
56
+ * @param {Object} settings - Settings object to check
57
+ * @param {string[]} foundList - Array to add found conflicts to
56
58
  */
57
- function detectConflictingSearch() {
58
- const result = {
59
- found: [],
60
- settings: null
61
- };
59
+ function checkSettingsForConflicts(settings, foundList) {
60
+ const mcpServers = settings.mcpServers || {};
62
61
 
63
- // Check if settings file exists
64
- if (!fs.existsSync(SETTINGS_PATH)) {
65
- return result;
66
- }
67
-
68
- // Read and parse settings
69
- try {
70
- const content = fs.readFileSync(SETTINGS_PATH, 'utf8');
71
- const settings = JSON.parse(content);
72
- result.settings = settings;
62
+ for (const serverId of Object.keys(mcpServers)) {
63
+ // Skip our own MCP
64
+ if (serverId === OUR_MCP_ID) {
65
+ continue;
66
+ }
73
67
 
74
- // Check mcpServers for conflicting entries
75
- const mcpServers = settings.mcpServers || {};
68
+ // Skip if already found
69
+ if (foundList.includes(serverId)) {
70
+ continue;
71
+ }
76
72
 
77
- for (const serverId of Object.keys(mcpServers)) {
78
- // Skip our own MCP
79
- if (serverId === OUR_MCP_ID) {
80
- continue;
73
+ // Check if server ID matches any conflicting pattern
74
+ const lowerServerId = serverId.toLowerCase();
75
+ for (const pattern of CONFLICTING_PATTERNS) {
76
+ if (lowerServerId.includes(pattern)) {
77
+ foundList.push(serverId);
78
+ break;
81
79
  }
80
+ }
82
81
 
83
- // Check if server ID matches any conflicting pattern
84
- const lowerServerId = serverId.toLowerCase();
82
+ // Also check if the command/args reference known conflicting tools
83
+ const config = mcpServers[serverId];
84
+ if (config && config.args) {
85
+ const argsStr = config.args.join(' ').toLowerCase();
85
86
  for (const pattern of CONFLICTING_PATTERNS) {
86
- if (lowerServerId.includes(pattern)) {
87
- result.found.push(serverId);
87
+ if (argsStr.includes(pattern) && !foundList.includes(serverId)) {
88
+ foundList.push(serverId);
88
89
  break;
89
90
  }
90
91
  }
92
+ }
91
93
 
92
- // Also check if the command/args reference known conflicting tools
93
- const config = mcpServers[serverId];
94
- if (config && config.args) {
95
- const argsStr = config.args.join(' ').toLowerCase();
96
- for (const pattern of CONFLICTING_PATTERNS) {
97
- if (argsStr.includes(pattern) && !result.found.includes(serverId)) {
98
- result.found.push(serverId);
99
- break;
100
- }
94
+ // Check command path for known binaries
95
+ if (config && config.command) {
96
+ const cmdLower = config.command.toLowerCase();
97
+ if (cmdLower.includes('/ck') || cmdLower.endsWith('/ck')) {
98
+ if (!foundList.includes(serverId)) {
99
+ foundList.push(serverId);
101
100
  }
102
101
  }
103
102
  }
104
- } catch (e) {
105
- // Can't parse settings, return empty
106
- console.warn(`Warning: Could not parse ${SETTINGS_PATH}: ${e.message}`);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Detect conflicting search MCPs in settings.
108
+ * Checks both ~/.claude/settings.json and ~/.claude.json
109
+ *
110
+ * @returns {{found: string[], settings: Object, altSettings: Object}} Found conflicting IDs and settings objects
111
+ */
112
+ function detectConflictingSearch() {
113
+ const result = {
114
+ found: [],
115
+ settings: null,
116
+ altSettings: null
117
+ };
118
+
119
+ // Check primary settings file (~/.claude/settings.json)
120
+ if (fs.existsSync(SETTINGS_PATH)) {
121
+ try {
122
+ const content = fs.readFileSync(SETTINGS_PATH, 'utf8');
123
+ result.settings = JSON.parse(content);
124
+ checkSettingsForConflicts(result.settings, result.found);
125
+ } catch (e) {
126
+ console.warn(`Warning: Could not parse ${SETTINGS_PATH}: ${e.message}`);
127
+ }
128
+ }
129
+
130
+ // Check alternate settings file (~/.claude.json)
131
+ if (fs.existsSync(ALT_SETTINGS_PATH)) {
132
+ try {
133
+ const content = fs.readFileSync(ALT_SETTINGS_PATH, 'utf8');
134
+ result.altSettings = JSON.parse(content);
135
+ checkSettingsForConflicts(result.altSettings, result.found);
136
+ } catch (e) {
137
+ console.warn(`Warning: Could not parse ${ALT_SETTINGS_PATH}: ${e.message}`);
138
+ }
107
139
  }
108
140
 
109
141
  return result;
110
142
  }
111
143
 
112
144
  /**
113
- * Disable conflicting MCPs by removing them from settings.
145
+ * Remove conflicting MCPs from a single settings file.
114
146
  *
147
+ * @param {string} settingsPath - Path to settings file
115
148
  * @param {string[]} conflictingIds - IDs of MCPs to remove
116
- * @returns {{disabled: string[], backupPath: string|null}} Disabled IDs and backup path
149
+ * @returns {{disabled: string[], backupPath: string|null}}
117
150
  */
118
- function disableConflictingMCPs(conflictingIds) {
151
+ function removeFromSettingsFile(settingsPath, conflictingIds) {
119
152
  const result = {
120
153
  disabled: [],
121
154
  backupPath: null
122
155
  };
123
156
 
124
- if (!conflictingIds || conflictingIds.length === 0) {
125
- return result;
126
- }
127
-
128
- if (!fs.existsSync(SETTINGS_PATH)) {
157
+ if (!fs.existsSync(settingsPath)) {
129
158
  return result;
130
159
  }
131
160
 
132
161
  try {
133
- // Read current settings
134
- const content = fs.readFileSync(SETTINGS_PATH, 'utf8');
162
+ const content = fs.readFileSync(settingsPath, 'utf8');
135
163
  const settings = JSON.parse(content);
136
164
 
137
- // Create backup with timestamp
138
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
139
- const backupPath = `${SETTINGS_PATH}.bak.${timestamp}`;
140
- fs.writeFileSync(backupPath, content, 'utf8');
141
- result.backupPath = backupPath;
142
-
143
- // Remove conflicting MCPs
144
165
  const mcpServers = settings.mcpServers || {};
166
+ let modified = false;
167
+
145
168
  for (const serverId of conflictingIds) {
146
169
  if (mcpServers[serverId]) {
170
+ // Create backup before first modification
171
+ if (!modified) {
172
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
173
+ result.backupPath = `${settingsPath}.bak.${timestamp}`;
174
+ fs.writeFileSync(result.backupPath, content, 'utf8');
175
+ }
176
+
147
177
  delete mcpServers[serverId];
148
178
  result.disabled.push(serverId);
179
+ modified = true;
180
+ console.log(`[migration] Removed ${serverId} from ${path.basename(settingsPath)}`);
149
181
  }
150
182
  }
151
183
 
152
- // Write updated settings
153
- fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2), 'utf8');
184
+ if (modified) {
185
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
186
+ }
154
187
  } catch (e) {
155
- console.error(`Error disabling conflicting MCPs: ${e.message}`);
188
+ console.error(`Error removing MCPs from ${settingsPath}: ${e.message}`);
189
+ }
190
+
191
+ return result;
192
+ }
193
+
194
+ /**
195
+ * Disable conflicting MCPs by removing them from all settings files.
196
+ * Checks both ~/.claude/settings.json and ~/.claude.json
197
+ *
198
+ * @param {string[]} conflictingIds - IDs of MCPs to remove
199
+ * @returns {{disabled: string[], backupPaths: string[]}} Disabled IDs and backup paths
200
+ */
201
+ function disableConflictingMCPs(conflictingIds) {
202
+ const result = {
203
+ disabled: [],
204
+ backupPath: null, // Keep for backwards compat
205
+ backupPaths: []
206
+ };
207
+
208
+ if (!conflictingIds || conflictingIds.length === 0) {
209
+ return result;
210
+ }
211
+
212
+ // Remove from primary settings file
213
+ const primary = removeFromSettingsFile(SETTINGS_PATH, conflictingIds);
214
+ result.disabled.push(...primary.disabled);
215
+ if (primary.backupPath) {
216
+ result.backupPaths.push(primary.backupPath);
217
+ result.backupPath = primary.backupPath;
218
+ }
219
+
220
+ // Remove from alternate settings file
221
+ const alt = removeFromSettingsFile(ALT_SETTINGS_PATH, conflictingIds);
222
+ for (const id of alt.disabled) {
223
+ if (!result.disabled.includes(id)) {
224
+ result.disabled.push(id);
225
+ }
226
+ }
227
+ if (alt.backupPath) {
228
+ result.backupPaths.push(alt.backupPath);
156
229
  }
157
230
 
158
231
  return result;
@@ -387,6 +460,7 @@ module.exports = {
387
460
  reindexWithQwen,
388
461
  // Expose constants for testing
389
462
  SETTINGS_PATH,
463
+ ALT_SETTINGS_PATH,
390
464
  CONFLICTING_PATTERNS,
391
465
  OLD_INDEX_DIRS,
392
466
  OUR_MCP_ID,
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "rrr-runtime",
3
+ "version": "1.0.0",
4
+ "description": "RRR runtime dependencies for semantic search",
5
+ "private": true,
6
+ "dependencies": {
7
+ "@lancedb/lancedb": "^0.23.0",
8
+ "tree-sitter": "^0.21.1",
9
+ "tree-sitter-go": "^0.21.0",
10
+ "tree-sitter-javascript": "^0.21.4",
11
+ "tree-sitter-python": "^0.21.0",
12
+ "tree-sitter-typescript": "^0.23.2"
13
+ }
14
+ }
@@ -27,11 +27,19 @@ const MCP_SERVER_ID = 'rrr-search';
27
27
 
28
28
  /**
29
29
  * Get the absolute path to the MCP server entry point.
30
+ * Prefers the installed location (~/.claude/rrr/mcp-server/index.js)
31
+ * over the npx/source location.
30
32
  *
31
33
  * @returns {string} Absolute path to rrr/mcp-server/index.js
32
34
  */
33
35
  function getMCPServerPath() {
34
- // Navigate from this script to the MCP server
36
+ // Prefer the installed location - this is stable across npx runs
37
+ const installedPath = path.join(os.homedir(), '.claude', 'rrr', 'mcp-server', 'index.js');
38
+ if (fs.existsSync(installedPath)) {
39
+ return installedPath;
40
+ }
41
+
42
+ // Fallback to relative path from script (for source/development)
35
43
  // scripts/register-mcp.js -> ../mcp-server/index.js
36
44
  return path.resolve(__dirname, '..', 'mcp-server', 'index.js');
37
45
  }
@@ -113,7 +121,7 @@ function writeSettings(settings) {
113
121
  * @returns {Promise<{registered: boolean, method: string, error?: string}>}
114
122
  */
115
123
  async function registerMCP(options = {}) {
116
- const {
124
+ let {
117
125
  forceCLI = false,
118
126
  forceJSON = false,
119
127
  runMigration = true
@@ -133,6 +141,21 @@ async function registerMCP(options = {}) {
133
141
  let method = 'json';
134
142
  let registered = false;
135
143
  let error = null;
144
+ let updated = false;
145
+
146
+ // Check if existing registration has wrong path (e.g., npx temp dir)
147
+ // If so, force JSON update to fix it
148
+ const existingSettings = readSettings();
149
+ const existingConfig = existingSettings.mcpServers?.[MCP_SERVER_ID];
150
+ if (existingConfig && existingConfig.args?.[0]) {
151
+ const existingPath = existingConfig.args[0];
152
+ // Detect stale npx paths or non-existent paths
153
+ if (existingPath.includes('/_npx/') || existingPath.includes('\\_npx\\') || !fs.existsSync(existingPath)) {
154
+ console.log(`[mcp] Updating stale MCP path: ${existingPath.slice(-50)}... -> ${mcpServerPath}`);
155
+ forceJSON = true; // Force JSON method to update
156
+ updated = true;
157
+ }
158
+ }
136
159
 
137
160
  // Try CLI method first (unless forceJSON)
138
161
  if (!forceJSON && (hasClaudeCLI() || forceCLI)) {
@@ -158,7 +181,7 @@ async function registerMCP(options = {}) {
158
181
  try {
159
182
  const settings = readSettings();
160
183
 
161
- // Add rrr-search to mcpServers
184
+ // Add or update rrr-search in mcpServers
162
185
  settings.mcpServers[MCP_SERVER_ID] = {
163
186
  command: 'node',
164
187
  args: [mcpServerPath]
@@ -166,7 +189,7 @@ async function registerMCP(options = {}) {
166
189
 
167
190
  const success = writeSettings(settings);
168
191
  if (success) {
169
- method = 'json';
192
+ method = updated ? 'json-updated' : 'json';
170
193
  registered = true;
171
194
  } else {
172
195
  error = 'Failed to write settings.json';