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 +35 -0
- package/package.json +2 -1
- package/rrr/lib/mcp/semantic-migration.js +134 -60
- package/rrr/package.json +14 -0
- package/rrr/scripts/register-mcp.js +27 -4
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.
|
|
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
|
|
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
|
-
*
|
|
54
|
+
* Check a settings object for conflicting MCPs.
|
|
54
55
|
*
|
|
55
|
-
* @
|
|
56
|
+
* @param {Object} settings - Settings object to check
|
|
57
|
+
* @param {string[]} foundList - Array to add found conflicts to
|
|
56
58
|
*/
|
|
57
|
-
function
|
|
58
|
-
const
|
|
59
|
-
found: [],
|
|
60
|
-
settings: null
|
|
61
|
-
};
|
|
59
|
+
function checkSettingsForConflicts(settings, foundList) {
|
|
60
|
+
const mcpServers = settings.mcpServers || {};
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
//
|
|
75
|
-
|
|
68
|
+
// Skip if already found
|
|
69
|
+
if (foundList.includes(serverId)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
76
72
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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 (
|
|
87
|
-
|
|
87
|
+
if (argsStr.includes(pattern) && !foundList.includes(serverId)) {
|
|
88
|
+
foundList.push(serverId);
|
|
88
89
|
break;
|
|
89
90
|
}
|
|
90
91
|
}
|
|
92
|
+
}
|
|
91
93
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
*
|
|
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}}
|
|
149
|
+
* @returns {{disabled: string[], backupPath: string|null}}
|
|
117
150
|
*/
|
|
118
|
-
function
|
|
151
|
+
function removeFromSettingsFile(settingsPath, conflictingIds) {
|
|
119
152
|
const result = {
|
|
120
153
|
disabled: [],
|
|
121
154
|
backupPath: null
|
|
122
155
|
};
|
|
123
156
|
|
|
124
|
-
if (!
|
|
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
|
-
|
|
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
|
-
|
|
153
|
-
|
|
184
|
+
if (modified) {
|
|
185
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
186
|
+
}
|
|
154
187
|
} catch (e) {
|
|
155
|
-
console.error(`Error
|
|
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,
|
package/rrr/package.json
ADDED
|
@@ -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
|
-
//
|
|
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
|
-
|
|
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
|
|
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';
|