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/.claude/settings.local.json.example +5 -0
- package/.claude-plugin/plugin.json +15 -2
- package/CHANGELOG.md +30 -0
- package/README.md +19 -2
- package/dist/index.js +63 -17
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/scripts/validate-npm-release.sh +211 -0
- package/src/cli/commands/plugin-api.test.ts +105 -43
- package/src/cli/commands/plugin-api.ts +42 -8
- package/src/mcp/plugin-mcp-config.test.ts +64 -0
- package/src/plugin/commands.ts +42 -17
- package/src/scripts/validate-npm-release.test.ts +34 -0
- package/.mcp.json +0 -13
- package/src/mcp/mcp-config.test.ts +0 -43
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bluera-knowledge",
|
|
3
|
-
"version": "0.11.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
56
|
+
export function createStoresCommand(getOptions: () => GlobalOptions): Command {
|
|
37
57
|
return new Command('stores').description('List all indexed library stores').action(async () => {
|
|
38
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
+
});
|