bluera-knowledge 0.13.0 → 0.13.2
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/.claude/rules/code-quality.md +12 -0
- package/.claude/rules/git.md +5 -0
- package/.claude/rules/versioning.md +7 -0
- package/.claude-plugin/plugin.json +2 -15
- package/.mcp.json +11 -0
- package/CHANGELOG.md +7 -0
- package/CLAUDE.md +5 -13
- package/CONTRIBUTING.md +307 -0
- package/README.md +58 -1167
- package/commands/crawl.md +2 -1
- package/commands/test-plugin.md +197 -72
- package/docs/claude-code-best-practices.md +458 -0
- package/docs/cli.md +170 -0
- package/docs/commands.md +392 -0
- package/docs/crawler-architecture.md +89 -0
- package/docs/mcp-integration.md +130 -0
- package/docs/token-efficiency.md +91 -0
- package/eslint.config.js +1 -1
- package/hooks/check-dependencies.sh +18 -1
- package/hooks/hooks.json +2 -2
- package/hooks/posttooluse-bk-reminder.py +30 -2
- package/package.json +1 -1
- package/scripts/test-mcp-dev.js +260 -0
- package/src/mcp/plugin-mcp-config.test.ts +26 -19
- package/tests/integration/cli-consistency.test.ts +3 -2
- package/docs/plans/2024-12-17-ai-search-quality-implementation.md +0 -752
- package/docs/plans/2024-12-17-ai-search-quality-testing-design.md +0 -201
- package/docs/plans/2025-12-16-bluera-knowledge-cli.md +0 -2951
- package/docs/plans/2025-12-16-phase2-features.md +0 -1518
- package/docs/plans/2025-12-17-hil-implementation.md +0 -926
- package/docs/plans/2025-12-17-hil-quality-testing.md +0 -224
- package/docs/plans/2025-12-17-search-quality-phase1-implementation.md +0 -1416
- package/docs/plans/2025-12-17-search-quality-testing-v2-design.md +0 -212
- package/docs/plans/2025-12-28-ai-agent-optimization.md +0 -1630
package/eslint.config.js
CHANGED
|
@@ -83,7 +83,7 @@ export default tseslint.config(
|
|
|
83
83
|
},
|
|
84
84
|
},
|
|
85
85
|
{
|
|
86
|
-
ignores: ['dist/**', 'node_modules/**', '*.config.js', '*.config.ts', '**/*.test.ts', 'tests/**/*.ts'],
|
|
86
|
+
ignores: ['dist/**', 'node_modules/**', 'scripts/**', '*.config.js', '*.config.ts', '**/*.test.ts', 'tests/**/*.ts'],
|
|
87
87
|
},
|
|
88
88
|
// Test files: Apply custom skip-comment rule only
|
|
89
89
|
{
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Bluera Knowledge Plugin - Dependency Checker
|
|
3
3
|
# Automatically checks and installs dependencies for the plugin
|
|
4
|
+
#
|
|
5
|
+
# Environment variables:
|
|
6
|
+
# BK_SKIP_AUTO_INSTALL=1 - Skip automatic pip installation of crawl4ai
|
|
7
|
+
# Set this if you prefer to manage Python packages manually
|
|
8
|
+
#
|
|
9
|
+
# What this script auto-installs (if missing):
|
|
10
|
+
# - Node.js dependencies (via bun or npm, from package.json)
|
|
11
|
+
# - crawl4ai Python package (via pip, for web crawling)
|
|
12
|
+
# - Playwright Chromium browser (via playwright CLI, for headless crawling)
|
|
4
13
|
|
|
5
14
|
set -e
|
|
6
15
|
|
|
@@ -95,9 +104,17 @@ echo -e "${YELLOW}To enable web crawling, install crawl4ai:${NC}"
|
|
|
95
104
|
echo -e " ${GREEN}pip install crawl4ai${NC}"
|
|
96
105
|
echo ""
|
|
97
106
|
|
|
107
|
+
# Check if auto-install is disabled
|
|
108
|
+
if [ "${BK_SKIP_AUTO_INSTALL:-}" = "1" ]; then
|
|
109
|
+
echo -e "${YELLOW}[bluera-knowledge] Auto-install disabled (BK_SKIP_AUTO_INSTALL=1)${NC}"
|
|
110
|
+
echo -e "${YELLOW}Install manually: pip install crawl4ai && python3 -m playwright install chromium${NC}"
|
|
111
|
+
exit 0
|
|
112
|
+
fi
|
|
113
|
+
|
|
98
114
|
# Check if we should auto-install
|
|
99
115
|
if command -v pip3 &> /dev/null || command -v pip &> /dev/null; then
|
|
100
|
-
echo -e "${YELLOW}
|
|
116
|
+
echo -e "${YELLOW}[bluera-knowledge] Auto-installing crawl4ai via pip...${NC}"
|
|
117
|
+
echo -e "${YELLOW}(Set BK_SKIP_AUTO_INSTALL=1 to disable auto-install)${NC}"
|
|
101
118
|
|
|
102
119
|
# Try to install using pip3 or pip
|
|
103
120
|
if command -v pip3 &> /dev/null; then
|
package/hooks/hooks.json
CHANGED
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
{
|
|
41
41
|
"type": "command",
|
|
42
42
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/job-status-hook.sh",
|
|
43
|
-
"timeout":
|
|
43
|
+
"timeout": 2
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
"type": "command",
|
|
47
47
|
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/skill-activation.py",
|
|
48
|
-
"timeout":
|
|
48
|
+
"timeout": 2
|
|
49
49
|
}
|
|
50
50
|
]
|
|
51
51
|
}
|
|
@@ -10,6 +10,20 @@ import re
|
|
|
10
10
|
import sys
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
|
+
# Fast string checks - if none of these are in the path, skip regex entirely
|
|
14
|
+
# This avoids regex overhead for the vast majority of file accesses
|
|
15
|
+
LIBRARY_QUICK_CHECKS = frozenset([
|
|
16
|
+
"node_modules",
|
|
17
|
+
"vendor",
|
|
18
|
+
"site-packages",
|
|
19
|
+
"venv",
|
|
20
|
+
"bower_components",
|
|
21
|
+
"packages",
|
|
22
|
+
".npm",
|
|
23
|
+
".cargo",
|
|
24
|
+
"go/pkg",
|
|
25
|
+
])
|
|
26
|
+
|
|
13
27
|
# Patterns indicating library/dependency code
|
|
14
28
|
LIBRARY_PATH_PATTERNS = [
|
|
15
29
|
r"node_modules/",
|
|
@@ -28,6 +42,12 @@ LIBRARY_PATH_PATTERNS = [
|
|
|
28
42
|
LIBRARY_PATTERNS_RE = re.compile("|".join(LIBRARY_PATH_PATTERNS), re.IGNORECASE)
|
|
29
43
|
|
|
30
44
|
|
|
45
|
+
def quick_path_check(path: str) -> bool:
|
|
46
|
+
"""Fast check if path might contain library code. Avoids regex for most paths."""
|
|
47
|
+
path_lower = path.lower()
|
|
48
|
+
return any(keyword in path_lower for keyword in LIBRARY_QUICK_CHECKS)
|
|
49
|
+
|
|
50
|
+
|
|
31
51
|
def extract_library_name(path: str) -> str | None:
|
|
32
52
|
"""Extract library name from dependency path."""
|
|
33
53
|
# node_modules/package-name/...
|
|
@@ -62,7 +82,11 @@ def check_grep_tool(tool_input: dict[str, Any]) -> tuple[str | None, str | None]
|
|
|
62
82
|
"""Check if Grep targeted library code. Returns (action, library_name)."""
|
|
63
83
|
path = tool_input.get("path", "")
|
|
64
84
|
|
|
65
|
-
|
|
85
|
+
# Fast path: skip regex if no library keywords present
|
|
86
|
+
if not path or not quick_path_check(path):
|
|
87
|
+
return None, None
|
|
88
|
+
|
|
89
|
+
if LIBRARY_PATTERNS_RE.search(path):
|
|
66
90
|
lib_name = extract_library_name(path)
|
|
67
91
|
return f"grepped in `{path}`", lib_name
|
|
68
92
|
|
|
@@ -73,7 +97,11 @@ def check_read_tool(tool_input: dict[str, Any]) -> tuple[str | None, str | None]
|
|
|
73
97
|
"""Check if Read targeted library code. Returns (action, library_name)."""
|
|
74
98
|
file_path = tool_input.get("file_path", "")
|
|
75
99
|
|
|
76
|
-
if
|
|
100
|
+
# Fast path: skip regex if no library keywords present
|
|
101
|
+
if not file_path or not quick_path_check(file_path):
|
|
102
|
+
return None, None
|
|
103
|
+
|
|
104
|
+
if LIBRARY_PATTERNS_RE.search(file_path):
|
|
77
105
|
lib_name = extract_library_name(file_path)
|
|
78
106
|
return f"read `{file_path}`", lib_name
|
|
79
107
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Development Test Helper
|
|
4
|
+
*
|
|
5
|
+
* Spawns the MCP server and communicates with it directly via JSON-RPC over stdio.
|
|
6
|
+
* Used by test-plugin --dev to test MCP functionality without needing the plugin installed.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ./scripts/test-mcp-dev.mjs call <tool-name> '<json-args>'
|
|
10
|
+
* ./scripts/test-mcp-dev.mjs call execute '{"command":"help"}'
|
|
11
|
+
* ./scripts/test-mcp-dev.mjs call search '{"query":"test"}'
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { spawn } from 'node:child_process';
|
|
15
|
+
import { createInterface } from 'node:readline';
|
|
16
|
+
import { dirname, join } from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
|
|
19
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const PROJECT_ROOT = join(__dirname, '..');
|
|
21
|
+
|
|
22
|
+
class MCPTestClient {
|
|
23
|
+
constructor() {
|
|
24
|
+
this.server = null;
|
|
25
|
+
this.messageId = 0;
|
|
26
|
+
this.pendingRequests = new Map();
|
|
27
|
+
this.initialized = false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async start() {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const serverPath = join(PROJECT_ROOT, 'dist', 'mcp', 'server.js');
|
|
33
|
+
|
|
34
|
+
this.server = spawn('node', [serverPath], {
|
|
35
|
+
cwd: PROJECT_ROOT,
|
|
36
|
+
env: {
|
|
37
|
+
...process.env,
|
|
38
|
+
PROJECT_ROOT: PROJECT_ROOT,
|
|
39
|
+
DATA_DIR: '.bluera/bluera-knowledge/data',
|
|
40
|
+
CONFIG_PATH: '.bluera/bluera-knowledge/config.json',
|
|
41
|
+
},
|
|
42
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Handle stderr (logs)
|
|
46
|
+
this.server.stderr.on('data', (data) => {
|
|
47
|
+
// Suppress server logs in test output unless DEBUG
|
|
48
|
+
if (process.env.DEBUG) {
|
|
49
|
+
process.stderr.write(`[server] ${data}`);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Handle stdout (JSON-RPC responses)
|
|
54
|
+
const rl = createInterface({ input: this.server.stdout });
|
|
55
|
+
rl.on('line', (line) => {
|
|
56
|
+
try {
|
|
57
|
+
const msg = JSON.parse(line);
|
|
58
|
+
if (msg.id !== undefined && this.pendingRequests.has(msg.id)) {
|
|
59
|
+
const { resolve, reject } = this.pendingRequests.get(msg.id);
|
|
60
|
+
this.pendingRequests.delete(msg.id);
|
|
61
|
+
if (msg.error) {
|
|
62
|
+
reject(new Error(msg.error.message || JSON.stringify(msg.error)));
|
|
63
|
+
} else {
|
|
64
|
+
resolve(msg.result);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
// Not JSON or parse error - ignore
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
this.server.on('error', reject);
|
|
73
|
+
this.server.on('spawn', () => {
|
|
74
|
+
// Give server a moment to initialize
|
|
75
|
+
setTimeout(() => resolve(), 100);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async initialize() {
|
|
81
|
+
if (this.initialized) return;
|
|
82
|
+
|
|
83
|
+
// Send initialize request
|
|
84
|
+
const initResult = await this.sendRequest('initialize', {
|
|
85
|
+
protocolVersion: '2024-11-05',
|
|
86
|
+
capabilities: {},
|
|
87
|
+
clientInfo: { name: 'test-mcp-dev', version: '1.0.0' },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Send initialized notification
|
|
91
|
+
this.sendNotification('notifications/initialized', {});
|
|
92
|
+
|
|
93
|
+
this.initialized = true;
|
|
94
|
+
return initResult;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
sendRequest(method, params) {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const id = ++this.messageId;
|
|
100
|
+
const request = { jsonrpc: '2.0', id, method, params };
|
|
101
|
+
|
|
102
|
+
this.pendingRequests.set(id, { resolve, reject });
|
|
103
|
+
|
|
104
|
+
// Set timeout for request
|
|
105
|
+
setTimeout(() => {
|
|
106
|
+
if (this.pendingRequests.has(id)) {
|
|
107
|
+
this.pendingRequests.delete(id);
|
|
108
|
+
reject(new Error(`Request timeout: ${method}`));
|
|
109
|
+
}
|
|
110
|
+
}, 30000);
|
|
111
|
+
|
|
112
|
+
this.server.stdin.write(JSON.stringify(request) + '\n');
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
sendNotification(method, params) {
|
|
117
|
+
const notification = { jsonrpc: '2.0', method, params };
|
|
118
|
+
this.server.stdin.write(JSON.stringify(notification) + '\n');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async callTool(name, args) {
|
|
122
|
+
if (!this.initialized) {
|
|
123
|
+
await this.initialize();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const result = await this.sendRequest('tools/call', {
|
|
127
|
+
name,
|
|
128
|
+
arguments: args,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async listTools() {
|
|
135
|
+
if (!this.initialized) {
|
|
136
|
+
await this.initialize();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return this.sendRequest('tools/list', {});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
stop() {
|
|
143
|
+
if (this.server) {
|
|
144
|
+
this.server.kill('SIGTERM');
|
|
145
|
+
this.server = null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Session mode - reads multiple commands from stdin, maintains server across calls
|
|
151
|
+
async function sessionMode() {
|
|
152
|
+
const client = new MCPTestClient();
|
|
153
|
+
await client.start();
|
|
154
|
+
|
|
155
|
+
const rl = createInterface({ input: process.stdin });
|
|
156
|
+
const results = [];
|
|
157
|
+
let lastResultId = null;
|
|
158
|
+
|
|
159
|
+
for await (const line of rl) {
|
|
160
|
+
const trimmed = line.trim();
|
|
161
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
162
|
+
|
|
163
|
+
const spaceIndex = trimmed.indexOf(' ');
|
|
164
|
+
const toolName = spaceIndex > 0 ? trimmed.slice(0, spaceIndex) : trimmed;
|
|
165
|
+
let argsStr = spaceIndex > 0 ? trimmed.slice(spaceIndex + 1) : '{}';
|
|
166
|
+
|
|
167
|
+
// Substitute $LAST_ID with actual result ID from previous search
|
|
168
|
+
if (lastResultId && argsStr.includes('$LAST_ID')) {
|
|
169
|
+
argsStr = argsStr.replace(/\$LAST_ID/g, lastResultId);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const args = JSON.parse(argsStr);
|
|
173
|
+
const result = await client.callTool(toolName, args);
|
|
174
|
+
results.push(result);
|
|
175
|
+
|
|
176
|
+
// Extract result ID for next call (search results have results[0].id)
|
|
177
|
+
// Search results have a header line before JSON, so find first '{'
|
|
178
|
+
if (result?.content?.[0]?.text) {
|
|
179
|
+
const text = result.content[0].text;
|
|
180
|
+
const jsonStart = text.indexOf('{');
|
|
181
|
+
if (jsonStart >= 0) {
|
|
182
|
+
try {
|
|
183
|
+
const parsed = JSON.parse(text.slice(jsonStart));
|
|
184
|
+
if (parsed.results?.[0]?.id) {
|
|
185
|
+
lastResultId = parsed.results[0].id;
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
// Not valid JSON or no results - ignore
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
client.stop();
|
|
195
|
+
console.log(JSON.stringify(results, null, 2));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// CLI interface
|
|
199
|
+
async function main() {
|
|
200
|
+
const args = process.argv.slice(2);
|
|
201
|
+
|
|
202
|
+
if (args.length < 1) {
|
|
203
|
+
console.error('Usage: test-mcp-dev.js <command> [args...]');
|
|
204
|
+
console.error('Commands:');
|
|
205
|
+
console.error(' call <tool-name> <json-args> - Call an MCP tool (one-shot)');
|
|
206
|
+
console.error(' session - Read commands from stdin (persistent server)');
|
|
207
|
+
console.error(' list - List available tools');
|
|
208
|
+
console.error('');
|
|
209
|
+
console.error('Examples:');
|
|
210
|
+
console.error(' ./scripts/test-mcp-dev.js call execute \'{"command":"help"}\'');
|
|
211
|
+
console.error(' ./scripts/test-mcp-dev.js call search \'{"query":"test"}\'');
|
|
212
|
+
console.error(' ./scripts/test-mcp-dev.js list');
|
|
213
|
+
console.error('');
|
|
214
|
+
console.error('Session mode (maintains cache across calls):');
|
|
215
|
+
console.error(' echo -e \'search {"query":"test"}\\nget_full_context {"resultId":"$LAST_ID"}\' | ./scripts/test-mcp-dev.js session');
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const command = args[0];
|
|
220
|
+
|
|
221
|
+
// Session mode handles its own client lifecycle
|
|
222
|
+
if (command === 'session') {
|
|
223
|
+
await sessionMode();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const client = new MCPTestClient();
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
await client.start();
|
|
231
|
+
|
|
232
|
+
if (command === 'call') {
|
|
233
|
+
if (args.length < 3) {
|
|
234
|
+
console.error('Usage: test-mcp-dev.js call <tool-name> <json-args>');
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const toolName = args[1];
|
|
239
|
+
const toolArgs = JSON.parse(args[2]);
|
|
240
|
+
|
|
241
|
+
const result = await client.callTool(toolName, toolArgs);
|
|
242
|
+
|
|
243
|
+
// Output result as JSON for parsing by test-plugin
|
|
244
|
+
console.log(JSON.stringify(result, null, 2));
|
|
245
|
+
} else if (command === 'list') {
|
|
246
|
+
const result = await client.listTools();
|
|
247
|
+
console.log(JSON.stringify(result, null, 2));
|
|
248
|
+
} else {
|
|
249
|
+
console.error(`Unknown command: ${command}`);
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.error(`Error: ${error.message}`);
|
|
254
|
+
process.exit(1);
|
|
255
|
+
} finally {
|
|
256
|
+
client.stop();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
main();
|
|
@@ -3,25 +3,32 @@ import { readFileSync, existsSync } from 'fs';
|
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Tests to verify
|
|
6
|
+
* Tests to verify .mcp.json is correctly configured for MCP server.
|
|
7
7
|
* The MCP server must work when the plugin is installed via marketplace.
|
|
8
8
|
*
|
|
9
9
|
* Key requirements:
|
|
10
10
|
* - Must use ${CLAUDE_PLUGIN_ROOT} for server path (resolves to plugin cache)
|
|
11
11
|
* - Must set PROJECT_ROOT env var (required by server fail-fast check)
|
|
12
12
|
* - Must NOT use relative paths (would resolve to user's project, not plugin)
|
|
13
|
+
*
|
|
14
|
+
* Note: We use .mcp.json at plugin root (not inline in plugin.json) due to
|
|
15
|
+
* Claude Code Bug #16143 where inline mcpServers is ignored during parsing.
|
|
16
|
+
* See: https://github.com/anthropics/claude-code/issues/16143
|
|
13
17
|
*/
|
|
14
|
-
describe('Plugin MCP Configuration (.
|
|
15
|
-
const
|
|
16
|
-
const config = JSON.parse(readFileSync(
|
|
18
|
+
describe('Plugin MCP Configuration (.mcp.json)', () => {
|
|
19
|
+
const mcpJsonPath = join(process.cwd(), '.mcp.json');
|
|
20
|
+
const config = JSON.parse(readFileSync(mcpJsonPath, 'utf-8'));
|
|
21
|
+
|
|
22
|
+
it('has .mcp.json file at plugin root', () => {
|
|
23
|
+
expect(existsSync(mcpJsonPath)).toBe(true);
|
|
24
|
+
});
|
|
17
25
|
|
|
18
|
-
it('has
|
|
19
|
-
expect(config).toHaveProperty('
|
|
20
|
-
expect(config.mcpServers).toHaveProperty('bluera-knowledge');
|
|
26
|
+
it('has bluera-knowledge server configuration', () => {
|
|
27
|
+
expect(config).toHaveProperty('bluera-knowledge');
|
|
21
28
|
});
|
|
22
29
|
|
|
23
30
|
it('uses ${CLAUDE_PLUGIN_ROOT} for server path (required for plugin mode)', () => {
|
|
24
|
-
const serverConfig = config
|
|
31
|
+
const serverConfig = config['bluera-knowledge'];
|
|
25
32
|
const argsString = JSON.stringify(serverConfig.args);
|
|
26
33
|
|
|
27
34
|
// CLAUDE_PLUGIN_ROOT is set by Claude Code when plugin is installed
|
|
@@ -31,7 +38,7 @@ describe('Plugin MCP Configuration (.claude-plugin/plugin.json)', () => {
|
|
|
31
38
|
});
|
|
32
39
|
|
|
33
40
|
it('does NOT use relative paths (would break in plugin mode)', () => {
|
|
34
|
-
const serverConfig = config
|
|
41
|
+
const serverConfig = config['bluera-knowledge'];
|
|
35
42
|
const argsString = JSON.stringify(serverConfig.args);
|
|
36
43
|
|
|
37
44
|
// Relative paths like ./dist would resolve to user's project directory
|
|
@@ -40,7 +47,7 @@ describe('Plugin MCP Configuration (.claude-plugin/plugin.json)', () => {
|
|
|
40
47
|
});
|
|
41
48
|
|
|
42
49
|
it('sets PROJECT_ROOT environment variable (required by fail-fast server)', () => {
|
|
43
|
-
const serverConfig = config
|
|
50
|
+
const serverConfig = config['bluera-knowledge'];
|
|
44
51
|
|
|
45
52
|
// PROJECT_ROOT is required since b404cd6 (fail-fast change)
|
|
46
53
|
expect(serverConfig.env).toHaveProperty('PROJECT_ROOT');
|
|
@@ -49,16 +56,16 @@ describe('Plugin MCP Configuration (.claude-plugin/plugin.json)', () => {
|
|
|
49
56
|
});
|
|
50
57
|
|
|
51
58
|
/**
|
|
52
|
-
* Tests to ensure .
|
|
53
|
-
*
|
|
59
|
+
* Tests to ensure plugin.json does NOT have inline mcpServers.
|
|
60
|
+
* Inline mcpServers is broken due to Claude Code Bug #16143.
|
|
54
61
|
*/
|
|
55
|
-
describe('
|
|
56
|
-
it('does NOT
|
|
57
|
-
const
|
|
62
|
+
describe('plugin.json does NOT have inline mcpServers', () => {
|
|
63
|
+
it('plugin.json does NOT contain mcpServers (would be ignored by Claude Code)', () => {
|
|
64
|
+
const pluginJsonPath = join(process.cwd(), '.claude-plugin/plugin.json');
|
|
65
|
+
const pluginConfig = JSON.parse(readFileSync(pluginJsonPath, 'utf-8'));
|
|
58
66
|
|
|
59
|
-
// .
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
expect(existsSync(mcpJsonPath)).toBe(false);
|
|
67
|
+
// mcpServers in plugin.json is ignored due to Bug #16143
|
|
68
|
+
// All MCP config should be in .mcp.json at plugin root
|
|
69
|
+
expect(pluginConfig).not.toHaveProperty('mcpServers');
|
|
63
70
|
});
|
|
64
71
|
});
|
|
@@ -182,10 +182,11 @@ describe('CLI Consistency', () => {
|
|
|
182
182
|
beforeAll(async () => {
|
|
183
183
|
try {
|
|
184
184
|
cli(`store create quiet-test-store --type file --source "${testFilesDir}"`);
|
|
185
|
+
cli('index quiet-test-store');
|
|
185
186
|
} catch {
|
|
186
187
|
// Store may already exist
|
|
187
188
|
}
|
|
188
|
-
});
|
|
189
|
+
}, 120000);
|
|
189
190
|
|
|
190
191
|
it('--quiet suppresses all output for index on success', () => {
|
|
191
192
|
const result = runCli('index quiet-test-store --quiet');
|
|
@@ -204,7 +205,7 @@ describe('CLI Consistency', () => {
|
|
|
204
205
|
});
|
|
205
206
|
|
|
206
207
|
it('--quiet outputs only paths for search', () => {
|
|
207
|
-
const result = runCli('search "test" --quiet');
|
|
208
|
+
const result = runCli('search "test" --stores quiet-test-store --quiet');
|
|
208
209
|
expect(result.exitCode).toBe(0);
|
|
209
210
|
// Should not contain verbose headers
|
|
210
211
|
expect(result.stdout).not.toContain('Search:');
|