bluera-knowledge 0.11.5 → 0.11.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bluera-knowledge",
3
- "version": "0.11.5",
3
+ "version": "0.11.7",
4
4
  "description": "CLI tool for managing knowledge stores with semantic search",
5
5
  "type": "module",
6
6
  "bin": {
@@ -38,7 +38,8 @@
38
38
  "version:patch": "bun run prerelease && commit-and-tag-version --release-as patch --skip.commit --skip.tag",
39
39
  "release:patch": "commit-and-tag-version --release-as patch && git push --follow-tags",
40
40
  "release:minor": "commit-and-tag-version --release-as minor && git push --follow-tags",
41
- "release:major": "commit-and-tag-version --release-as major && git push --follow-tags"
41
+ "release:major": "commit-and-tag-version --release-as major && git push --follow-tags",
42
+ "validate:npm": "./scripts/validate-npm-release.sh"
42
43
  },
43
44
  "keywords": [
44
45
  "knowledge",
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # validate-npm-release.sh
4
+ #
5
+ # Post-release validation script for bluera-knowledge npm module.
6
+ # Installs the latest version from npm and exercises all CLI commands.
7
+ #
8
+ # Usage:
9
+ # ./scripts/validate-npm-release.sh
10
+ #
11
+ # Output:
12
+ # Logs written to: <repo>/logs/validation/npm-validation-YYYYMMDD-HHMMSS.log
13
+ # Exit code: 0 if all tests pass, 1 if any fail
14
+ #
15
+
16
+ set -euo pipefail
17
+
18
+ # Configuration
19
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
20
+ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
21
+ RESULTS_DIR="$REPO_ROOT/logs/validation"
22
+ TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
23
+ LOG_FILE="$RESULTS_DIR/npm-validation-$TIMESTAMP.log"
24
+
25
+ # Test configuration
26
+ TEST_STORE="npm-validation-test-$TIMESTAMP"
27
+ TEST_FOLDER="$(mktemp -d)"
28
+ DATA_DIR="$(mktemp -d)"
29
+
30
+ # Counters
31
+ TESTS_RUN=0
32
+ TESTS_PASSED=0
33
+ TESTS_FAILED=0
34
+
35
+ # Setup
36
+ mkdir -p "$RESULTS_DIR"
37
+ echo "Test content for validation" > "$TEST_FOLDER/test.txt"
38
+ echo "Another test file" > "$TEST_FOLDER/test2.md"
39
+
40
+ # Logging functions
41
+ log() {
42
+ echo "[$(date +%H:%M:%S)] $*" | tee -a "$LOG_FILE"
43
+ }
44
+
45
+ log_header() {
46
+ echo "" | tee -a "$LOG_FILE"
47
+ echo "========================================" | tee -a "$LOG_FILE"
48
+ echo "$*" | tee -a "$LOG_FILE"
49
+ echo "========================================" | tee -a "$LOG_FILE"
50
+ }
51
+
52
+ pass() {
53
+ TESTS_RUN=$((TESTS_RUN + 1))
54
+ TESTS_PASSED=$((TESTS_PASSED + 1))
55
+ log "✓ PASS: $*"
56
+ }
57
+
58
+ fail() {
59
+ TESTS_RUN=$((TESTS_RUN + 1))
60
+ TESTS_FAILED=$((TESTS_FAILED + 1))
61
+ log "✗ FAIL: $*"
62
+ }
63
+
64
+ # Run a command and check exit code
65
+ run_test() {
66
+ local name="$1"
67
+ shift
68
+ local cmd="$*"
69
+
70
+ log "Running: $cmd"
71
+ if eval "$cmd" >> "$LOG_FILE" 2>&1; then
72
+ pass "$name"
73
+ return 0
74
+ else
75
+ fail "$name (exit code: $?)"
76
+ return 1
77
+ fi
78
+ }
79
+
80
+ # Run a command and check output contains expected string
81
+ run_test_contains() {
82
+ local name="$1"
83
+ local expected="$2"
84
+ shift 2
85
+ local cmd="$*"
86
+
87
+ log "Running: $cmd"
88
+ local output
89
+ if output=$(eval "$cmd" 2>&1); then
90
+ if echo "$output" | grep -q "$expected"; then
91
+ pass "$name"
92
+ echo "$output" >> "$LOG_FILE"
93
+ return 0
94
+ else
95
+ fail "$name (output missing: $expected)"
96
+ echo "$output" >> "$LOG_FILE"
97
+ return 1
98
+ fi
99
+ else
100
+ fail "$name (command failed)"
101
+ echo "$output" >> "$LOG_FILE"
102
+ return 1
103
+ fi
104
+ }
105
+
106
+ # Cleanup function
107
+ cleanup() {
108
+ log_header "Cleanup"
109
+
110
+ # Delete test store if it exists
111
+ log "Deleting test store: $TEST_STORE"
112
+ bluera-knowledge store delete "$TEST_STORE" --force -d "$DATA_DIR" 2>/dev/null || true
113
+
114
+ # Remove test folder
115
+ log "Removing test folder: $TEST_FOLDER"
116
+ rm -rf "$TEST_FOLDER"
117
+
118
+ # Remove data directory
119
+ log "Removing data directory: $DATA_DIR"
120
+ rm -rf "$DATA_DIR"
121
+
122
+ log "Cleanup complete"
123
+ }
124
+
125
+ # Set trap for cleanup on exit
126
+ trap cleanup EXIT
127
+
128
+ # Start validation
129
+ log_header "NPM Module Validation - $TIMESTAMP"
130
+ log "Log file: $LOG_FILE"
131
+ log "Test store: $TEST_STORE"
132
+ log "Test folder: $TEST_FOLDER"
133
+ log "Data directory: $DATA_DIR"
134
+
135
+ # Install latest from npm
136
+ log_header "Installing Latest from npm"
137
+ log "Installing bluera-knowledge@latest globally..."
138
+ if npm install -g bluera-knowledge@latest >> "$LOG_FILE" 2>&1; then
139
+ pass "npm install -g bluera-knowledge@latest"
140
+ else
141
+ fail "npm install -g bluera-knowledge@latest"
142
+ log "ERROR: Failed to install package. Aborting."
143
+ exit 1
144
+ fi
145
+
146
+ # Verify installation
147
+ log_header "Verifying Installation"
148
+
149
+ run_test_contains "bluera-knowledge --version" "0." "bluera-knowledge --version"
150
+
151
+ run_test_contains "bluera-knowledge --help" "CLI tool for managing knowledge stores" "bluera-knowledge --help"
152
+
153
+ # Test stores list (should work even if empty)
154
+ log_header "Testing Store Operations"
155
+
156
+ run_test "bluera-knowledge stores (initial list)" "bluera-knowledge stores -d '$DATA_DIR' -f json"
157
+
158
+ # Create a store via add-folder
159
+ log_header "Testing add-folder"
160
+
161
+ run_test "bluera-knowledge add-folder" "bluera-knowledge add-folder '$TEST_FOLDER' --name '$TEST_STORE' -d '$DATA_DIR'"
162
+
163
+ # Verify store was created
164
+ run_test_contains "Store appears in list" "$TEST_STORE" "bluera-knowledge stores -d '$DATA_DIR'"
165
+
166
+ # Test store info
167
+ log_header "Testing store info"
168
+
169
+ run_test_contains "bluera-knowledge store info" "$TEST_STORE" "bluera-knowledge store info '$TEST_STORE' -d '$DATA_DIR'"
170
+
171
+ # Test search (may return no results, but should not error)
172
+ log_header "Testing search"
173
+
174
+ run_test "bluera-knowledge search" "bluera-knowledge search 'test content' --store '$TEST_STORE' -d '$DATA_DIR' -f json"
175
+
176
+ # Test index command (re-index)
177
+ log_header "Testing index"
178
+
179
+ run_test "bluera-knowledge index" "bluera-knowledge index '$TEST_STORE' -d '$DATA_DIR'"
180
+
181
+ # Test search again after re-indexing
182
+ run_test "bluera-knowledge search (after reindex)" "bluera-knowledge search 'validation' --store '$TEST_STORE' -d '$DATA_DIR' -f json"
183
+
184
+ # Test store delete
185
+ log_header "Testing store delete"
186
+
187
+ run_test "bluera-knowledge store delete" "bluera-knowledge store delete '$TEST_STORE' --force -d '$DATA_DIR'"
188
+
189
+ # Verify store was deleted
190
+ log "Verifying store was deleted..."
191
+ if bluera-knowledge stores -d "$DATA_DIR" -f json 2>&1 | grep -q "$TEST_STORE"; then
192
+ fail "Store still exists after delete"
193
+ else
194
+ pass "Store successfully deleted"
195
+ fi
196
+
197
+ # Summary
198
+ log_header "Validation Summary"
199
+ log "Tests run: $TESTS_RUN"
200
+ log "Tests passed: $TESTS_PASSED"
201
+ log "Tests failed: $TESTS_FAILED"
202
+ log ""
203
+ log "Log file: $LOG_FILE"
204
+
205
+ if [ "$TESTS_FAILED" -gt 0 ]; then
206
+ log "VALIDATION FAILED"
207
+ exit 1
208
+ else
209
+ log "VALIDATION PASSED"
210
+ exit 0
211
+ fi
@@ -38,16 +38,25 @@ describe('Plugin API Commands - Execution Tests', () => {
38
38
  });
39
39
 
40
40
  describe('add-repo command', () => {
41
- it('calls handleAddRepo with url', async () => {
41
+ const expectedGlobalOpts = {
42
+ config: undefined,
43
+ dataDir: '/tmp/test',
44
+ projectRoot: undefined,
45
+ format: undefined,
46
+ quiet: false,
47
+ };
48
+
49
+ it('calls handleAddRepo with url and global options', async () => {
42
50
  const { handleAddRepo } = await import('../../plugin/commands.js');
43
51
 
44
52
  const command = createAddRepoCommand(getOptions);
45
53
  const actionHandler = (command as any)._actionHandler;
46
54
  await actionHandler(['https://github.com/user/repo.git']);
47
55
 
48
- expect(handleAddRepo).toHaveBeenCalledWith({
49
- url: 'https://github.com/user/repo.git',
50
- });
56
+ expect(handleAddRepo).toHaveBeenCalledWith(
57
+ { url: 'https://github.com/user/repo.git' },
58
+ expectedGlobalOpts
59
+ );
51
60
  });
52
61
 
53
62
  it('calls handleAddRepo with url and name option', async () => {
@@ -58,10 +67,10 @@ describe('Plugin API Commands - Execution Tests', () => {
58
67
  command.parseOptions(['--name', 'my-repo']);
59
68
  await actionHandler(['https://github.com/user/repo.git']);
60
69
 
61
- expect(handleAddRepo).toHaveBeenCalledWith({
62
- url: 'https://github.com/user/repo.git',
63
- name: 'my-repo',
64
- });
70
+ expect(handleAddRepo).toHaveBeenCalledWith(
71
+ { url: 'https://github.com/user/repo.git', name: 'my-repo' },
72
+ expectedGlobalOpts
73
+ );
65
74
  });
66
75
 
67
76
  it('calls handleAddRepo with url and branch option', async () => {
@@ -72,10 +81,10 @@ describe('Plugin API Commands - Execution Tests', () => {
72
81
  command.parseOptions(['--branch', 'develop']);
73
82
  await actionHandler(['https://github.com/user/repo.git']);
74
83
 
75
- expect(handleAddRepo).toHaveBeenCalledWith({
76
- url: 'https://github.com/user/repo.git',
77
- branch: 'develop',
78
- });
84
+ expect(handleAddRepo).toHaveBeenCalledWith(
85
+ { url: 'https://github.com/user/repo.git', branch: 'develop' },
86
+ expectedGlobalOpts
87
+ );
79
88
  });
80
89
 
81
90
  it('calls handleAddRepo with all options', async () => {
@@ -86,11 +95,10 @@ describe('Plugin API Commands - Execution Tests', () => {
86
95
  command.parseOptions(['--name', 'custom-name', '--branch', 'main']);
87
96
  await actionHandler(['https://github.com/user/repo.git']);
88
97
 
89
- expect(handleAddRepo).toHaveBeenCalledWith({
90
- url: 'https://github.com/user/repo.git',
91
- name: 'custom-name',
92
- branch: 'main',
93
- });
98
+ expect(handleAddRepo).toHaveBeenCalledWith(
99
+ { url: 'https://github.com/user/repo.git', name: 'custom-name', branch: 'main' },
100
+ expectedGlobalOpts
101
+ );
94
102
  });
95
103
 
96
104
  it('handles errors from handleAddRepo', async () => {
@@ -108,16 +116,22 @@ describe('Plugin API Commands - Execution Tests', () => {
108
116
  });
109
117
 
110
118
  describe('add-folder command', () => {
111
- it('calls handleAddFolder with path', async () => {
119
+ const expectedGlobalOpts = {
120
+ config: undefined,
121
+ dataDir: '/tmp/test',
122
+ projectRoot: undefined,
123
+ format: undefined,
124
+ quiet: false,
125
+ };
126
+
127
+ it('calls handleAddFolder with path and global options', async () => {
112
128
  const { handleAddFolder } = await import('../../plugin/commands.js');
113
129
 
114
130
  const command = createAddFolderCommand(getOptions);
115
131
  const actionHandler = (command as any)._actionHandler;
116
132
  await actionHandler(['/path/to/folder']);
117
133
 
118
- expect(handleAddFolder).toHaveBeenCalledWith({
119
- path: '/path/to/folder',
120
- });
134
+ expect(handleAddFolder).toHaveBeenCalledWith({ path: '/path/to/folder' }, expectedGlobalOpts);
121
135
  });
122
136
 
123
137
  it('calls handleAddFolder with path and name option', async () => {
@@ -128,10 +142,10 @@ describe('Plugin API Commands - Execution Tests', () => {
128
142
  command.parseOptions(['--name', 'my-folder']);
129
143
  await actionHandler(['/path/to/folder']);
130
144
 
131
- expect(handleAddFolder).toHaveBeenCalledWith({
132
- path: '/path/to/folder',
133
- name: 'my-folder',
134
- });
145
+ expect(handleAddFolder).toHaveBeenCalledWith(
146
+ { path: '/path/to/folder', name: 'my-folder' },
147
+ expectedGlobalOpts
148
+ );
135
149
  });
136
150
 
137
151
  it('handles relative paths', async () => {
@@ -141,9 +155,7 @@ describe('Plugin API Commands - Execution Tests', () => {
141
155
  const actionHandler = (command as any)._actionHandler;
142
156
  await actionHandler(['./relative/path']);
143
157
 
144
- expect(handleAddFolder).toHaveBeenCalledWith({
145
- path: './relative/path',
146
- });
158
+ expect(handleAddFolder).toHaveBeenCalledWith({ path: './relative/path' }, expectedGlobalOpts);
147
159
  });
148
160
 
149
161
  it('handles paths with spaces', async () => {
@@ -153,9 +165,10 @@ describe('Plugin API Commands - Execution Tests', () => {
153
165
  const actionHandler = (command as any)._actionHandler;
154
166
  await actionHandler(['/path/with spaces/folder']);
155
167
 
156
- expect(handleAddFolder).toHaveBeenCalledWith({
157
- path: '/path/with spaces/folder',
158
- });
168
+ expect(handleAddFolder).toHaveBeenCalledWith(
169
+ { path: '/path/with spaces/folder' },
170
+ expectedGlobalOpts
171
+ );
159
172
  });
160
173
 
161
174
  it('handles errors from handleAddFolder', async () => {
@@ -171,14 +184,20 @@ describe('Plugin API Commands - Execution Tests', () => {
171
184
  });
172
185
 
173
186
  describe('stores command', () => {
174
- it('calls handleStores with no arguments', async () => {
187
+ it('calls handleStores with global options', async () => {
175
188
  const { handleStores } = await import('../../plugin/commands.js');
176
189
 
177
190
  const command = createStoresCommand(getOptions);
178
191
  const actionHandler = (command as any)._actionHandler;
179
192
  await actionHandler([]);
180
193
 
181
- expect(handleStores).toHaveBeenCalledWith();
194
+ expect(handleStores).toHaveBeenCalledWith({
195
+ config: undefined,
196
+ dataDir: '/tmp/test',
197
+ projectRoot: undefined,
198
+ format: undefined,
199
+ quiet: false,
200
+ });
182
201
  });
183
202
 
184
203
  it('calls handleStores exactly once', async () => {
@@ -204,14 +223,20 @@ describe('Plugin API Commands - Execution Tests', () => {
204
223
  });
205
224
 
206
225
  describe('suggest command', () => {
207
- it('calls handleSuggest with no arguments', async () => {
226
+ it('calls handleSuggest with global options', async () => {
208
227
  const { handleSuggest } = await import('../../plugin/commands.js');
209
228
 
210
229
  const command = createSuggestCommand(getOptions);
211
230
  const actionHandler = (command as any)._actionHandler;
212
231
  await actionHandler([]);
213
232
 
214
- expect(handleSuggest).toHaveBeenCalledWith();
233
+ expect(handleSuggest).toHaveBeenCalledWith({
234
+ config: undefined,
235
+ dataDir: '/tmp/test',
236
+ projectRoot: undefined,
237
+ format: undefined,
238
+ quiet: false,
239
+ });
215
240
  });
216
241
 
217
242
  it('calls handleSuggest exactly once', async () => {
@@ -283,28 +308,65 @@ describe('Plugin API Commands - Execution Tests', () => {
283
308
  });
284
309
 
285
310
  describe('global options handling', () => {
286
- it('add-repo ignores global options', async () => {
311
+ it('add-repo passes global options', async () => {
287
312
  const { handleAddRepo } = await import('../../plugin/commands.js');
288
313
 
289
- // Global options should not be passed to handlers
290
314
  const command = createAddRepoCommand(getOptions);
291
315
  const actionHandler = (command as any)._actionHandler;
292
316
  await actionHandler(['https://github.com/user/repo.git']);
293
317
 
294
- expect(handleAddRepo).toHaveBeenCalledWith({
295
- url: 'https://github.com/user/repo.git',
296
- });
318
+ expect(handleAddRepo).toHaveBeenCalledWith(
319
+ { url: 'https://github.com/user/repo.git' },
320
+ {
321
+ config: undefined,
322
+ dataDir: '/tmp/test',
323
+ projectRoot: undefined,
324
+ format: undefined,
325
+ quiet: false,
326
+ }
327
+ );
297
328
  });
298
329
 
299
- it('add-folder ignores global options', async () => {
330
+ it('add-folder passes global options', async () => {
300
331
  const { handleAddFolder } = await import('../../plugin/commands.js');
301
332
 
302
333
  const command = createAddFolderCommand(getOptions);
303
334
  const actionHandler = (command as any)._actionHandler;
304
335
  await actionHandler(['/path']);
305
336
 
306
- expect(handleAddFolder).toHaveBeenCalledWith({
307
- path: '/path',
337
+ expect(handleAddFolder).toHaveBeenCalledWith(
338
+ { path: '/path' },
339
+ {
340
+ config: undefined,
341
+ dataDir: '/tmp/test',
342
+ projectRoot: undefined,
343
+ format: undefined,
344
+ quiet: false,
345
+ }
346
+ );
347
+ });
348
+
349
+ it('stores passes dataDir from global options', async () => {
350
+ const { handleStores } = await import('../../plugin/commands.js');
351
+
352
+ const customGetOptions = (): GlobalOptions => ({
353
+ config: '/custom/config.json',
354
+ dataDir: '/custom/data',
355
+ quiet: true,
356
+ format: 'json',
357
+ projectRoot: '/my/project',
358
+ });
359
+
360
+ const command = createStoresCommand(customGetOptions);
361
+ const actionHandler = (command as any)._actionHandler;
362
+ await actionHandler([]);
363
+
364
+ expect(handleStores).toHaveBeenCalledWith({
365
+ config: '/custom/config.json',
366
+ dataDir: '/custom/data',
367
+ projectRoot: '/my/project',
368
+ format: 'json',
369
+ quiet: true,
308
370
  });
309
371
  });
310
372
  });
@@ -12,37 +12,71 @@ import type { GlobalOptions } from '../program.js';
12
12
  * These commands provide a simpler interface that matches the plugin commands.
13
13
  */
14
14
 
15
- export function createAddRepoCommand(_getOptions: () => GlobalOptions): Command {
15
+ export function createAddRepoCommand(getOptions: () => GlobalOptions): Command {
16
16
  return new Command('add-repo')
17
17
  .description('Clone and index a library source repository')
18
18
  .argument('<url>', 'Git repository URL')
19
19
  .option('--name <name>', 'Store name (defaults to repo name)')
20
20
  .option('--branch <branch>', 'Git branch to clone')
21
21
  .action(async (url: string, options: { name?: string; branch?: string }) => {
22
- await handleAddRepo({ url, ...options });
22
+ const globalOpts = getOptions();
23
+ await handleAddRepo(
24
+ { url, ...options },
25
+ {
26
+ config: globalOpts.config,
27
+ dataDir: globalOpts.dataDir,
28
+ projectRoot: globalOpts.projectRoot,
29
+ format: globalOpts.format,
30
+ quiet: globalOpts.quiet,
31
+ }
32
+ );
23
33
  });
24
34
  }
25
35
 
26
- export function createAddFolderCommand(_getOptions: () => GlobalOptions): Command {
36
+ export function createAddFolderCommand(getOptions: () => GlobalOptions): Command {
27
37
  return new Command('add-folder')
28
38
  .description('Index a local folder of reference material')
29
39
  .argument('<path>', 'Folder path to index')
30
40
  .option('--name <name>', 'Store name (defaults to folder name)')
31
41
  .action(async (path: string, options: { name?: string }) => {
32
- await handleAddFolder({ path, ...options });
42
+ const globalOpts = getOptions();
43
+ await handleAddFolder(
44
+ { path, ...options },
45
+ {
46
+ config: globalOpts.config,
47
+ dataDir: globalOpts.dataDir,
48
+ projectRoot: globalOpts.projectRoot,
49
+ format: globalOpts.format,
50
+ quiet: globalOpts.quiet,
51
+ }
52
+ );
33
53
  });
34
54
  }
35
55
 
36
- export function createStoresCommand(_getOptions: () => GlobalOptions): Command {
56
+ export function createStoresCommand(getOptions: () => GlobalOptions): Command {
37
57
  return new Command('stores').description('List all indexed library stores').action(async () => {
38
- await handleStores();
58
+ const globalOpts = getOptions();
59
+ await handleStores({
60
+ config: globalOpts.config,
61
+ dataDir: globalOpts.dataDir,
62
+ projectRoot: globalOpts.projectRoot,
63
+ format: globalOpts.format,
64
+ quiet: globalOpts.quiet,
65
+ });
39
66
  });
40
67
  }
41
68
 
42
- export function createSuggestCommand(_getOptions: () => GlobalOptions): Command {
69
+ export function createSuggestCommand(getOptions: () => GlobalOptions): Command {
43
70
  return new Command('suggest')
44
71
  .description('Suggest important dependencies to add to knowledge stores')
45
72
  .action(async () => {
46
- await handleSuggest();
73
+ const globalOpts = getOptions();
74
+ await handleSuggest({
75
+ config: globalOpts.config,
76
+ dataDir: globalOpts.dataDir,
77
+ projectRoot: globalOpts.projectRoot,
78
+ format: globalOpts.format,
79
+ quiet: globalOpts.quiet,
80
+ });
47
81
  });
48
82
  }
@@ -0,0 +1,64 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { readFileSync, existsSync } from 'fs';
3
+ import { join } from 'path';
4
+
5
+ /**
6
+ * Tests to verify plugin.json is correctly configured for MCP server.
7
+ * The MCP server must work when the plugin is installed via marketplace.
8
+ *
9
+ * Key requirements:
10
+ * - Must use ${CLAUDE_PLUGIN_ROOT} for server path (resolves to plugin cache)
11
+ * - Must set PROJECT_ROOT env var (required by server fail-fast check)
12
+ * - Must NOT use relative paths (would resolve to user's project, not plugin)
13
+ */
14
+ describe('Plugin MCP Configuration (.claude-plugin/plugin.json)', () => {
15
+ const configPath = join(process.cwd(), '.claude-plugin/plugin.json');
16
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
17
+
18
+ it('has mcpServers configuration inline', () => {
19
+ expect(config).toHaveProperty('mcpServers');
20
+ expect(config.mcpServers).toHaveProperty('bluera-knowledge');
21
+ });
22
+
23
+ it('uses ${CLAUDE_PLUGIN_ROOT} for server path (required for plugin mode)', () => {
24
+ const serverConfig = config.mcpServers['bluera-knowledge'];
25
+ const argsString = JSON.stringify(serverConfig.args);
26
+
27
+ // CLAUDE_PLUGIN_ROOT is set by Claude Code when plugin is installed
28
+ // This ensures the path resolves to the plugin cache, not user's project
29
+ expect(argsString).toContain('${CLAUDE_PLUGIN_ROOT}');
30
+ expect(argsString).toContain('dist/mcp/server.js');
31
+ });
32
+
33
+ it('does NOT use relative paths (would break in plugin mode)', () => {
34
+ const serverConfig = config.mcpServers['bluera-knowledge'];
35
+ const argsString = JSON.stringify(serverConfig.args);
36
+
37
+ // Relative paths like ./dist would resolve to user's project directory
38
+ // which doesn't have the plugin's dist folder
39
+ expect(argsString).not.toMatch(/"\.\//);
40
+ });
41
+
42
+ it('sets PROJECT_ROOT environment variable (required by fail-fast server)', () => {
43
+ const serverConfig = config.mcpServers['bluera-knowledge'];
44
+
45
+ // PROJECT_ROOT is required since b404cd6 (fail-fast change)
46
+ expect(serverConfig.env).toHaveProperty('PROJECT_ROOT');
47
+ expect(serverConfig.env['PROJECT_ROOT']).toBe('${PWD}');
48
+ });
49
+ });
50
+
51
+ /**
52
+ * Tests to ensure .mcp.json is NOT distributed with the plugin.
53
+ * .mcp.json at project root causes confusion between plugin and project config.
54
+ */
55
+ describe('No conflicting .mcp.json in repo', () => {
56
+ it('does NOT have .mcp.json in repo root (prevents config confusion)', () => {
57
+ const mcpJsonPath = join(process.cwd(), '.mcp.json');
58
+
59
+ // .mcp.json should NOT exist in the repo
60
+ // - For plugin mode: use mcpServers in plugin.json
61
+ // - For development: use ~/.claude.json per README
62
+ expect(existsSync(mcpJsonPath)).toBe(false);
63
+ });
64
+ });