bluera-knowledge 0.15.3 → 0.15.4

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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "bluera-knowledge",
3
- "version": "0.15.3",
3
+ "version": "0.15.4",
4
4
  "description": "Clone repos, crawl docs, search locally. Fast, authoritative answers for AI coding agents."
5
5
  }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
4
4
 
5
+ ## [0.15.4](https://github.com/blueraai/bluera-knowledge/compare/v0.15.3...v0.15.4) (2026-01-17)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **mcp:** use JavaScript bootstrap instead of bash wrapper ([960a401](https://github.com/blueraai/bluera-knowledge/commit/960a401ea8aa3d294f349e428732ae01bcad2571))
11
+
5
12
  ## [0.15.3](https://github.com/blueraai/bluera-knowledge/compare/v0.15.1...v0.15.3) (2026-01-16)
6
13
 
7
14
 
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/mcp/bootstrap.ts
4
+ import { execSync } from "child_process";
5
+ import { existsSync } from "fs";
6
+ import { dirname, join } from "path";
7
+ import { fileURLToPath } from "url";
8
+ var __filename = fileURLToPath(import.meta.url);
9
+ var __dirname = dirname(__filename);
10
+ var pluginRoot = join(__dirname, "..", "..");
11
+ if (!existsSync(join(pluginRoot, "node_modules"))) {
12
+ const hasBun = (() => {
13
+ try {
14
+ execSync("which bun", { stdio: "ignore" });
15
+ return true;
16
+ } catch {
17
+ return false;
18
+ }
19
+ })();
20
+ const cmd = hasBun ? "bun install --frozen-lockfile" : "npm ci --silent";
21
+ execSync(cmd, { cwd: pluginRoot, stdio: "inherit" });
22
+ }
23
+ var { runMCPServer } = await import("./server.js");
24
+ var projectRoot = process.env["PROJECT_ROOT"];
25
+ if (projectRoot === void 0) {
26
+ throw new Error("PROJECT_ROOT environment variable is required");
27
+ }
28
+ await runMCPServer({
29
+ dataDir: process.env["DATA_DIR"],
30
+ config: process.env["CONFIG_PATH"],
31
+ projectRoot
32
+ });
33
+ //# sourceMappingURL=bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/mcp/bootstrap.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * MCP Server Bootstrap - installs dependencies before starting server.\n *\n * Uses only Node.js built-ins (no external dependencies required).\n * Self-locates plugin root via import.meta.url (doesn't rely on CLAUDE_PLUGIN_ROOT).\n *\n * This solves two issues:\n * 1. Bash path resolution broken - ${CLAUDE_PLUGIN_ROOT:-.} not expanded for bash\n * 2. Dependencies not installed - plugins installed via git clone without npm install\n */\nimport { execSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n// Self-locate plugin root from this file's path\n// dist/mcp/bootstrap.js -> plugin root (two directories up)\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst pluginRoot = join(__dirname, '..', '..');\n\n// Install dependencies if node_modules is missing\nif (!existsSync(join(pluginRoot, 'node_modules'))) {\n const hasBun = ((): boolean => {\n try {\n execSync('which bun', { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n })();\n\n const cmd = hasBun ? 'bun install --frozen-lockfile' : 'npm ci --silent';\n execSync(cmd, { cwd: pluginRoot, stdio: 'inherit' });\n}\n\n// Now that dependencies are installed, import and run the server\n// Dynamic import required because @modelcontextprotocol/sdk wouldn't be available before install\nconst { runMCPServer } = await import('./server.js');\n\nconst projectRoot = process.env['PROJECT_ROOT'];\nif (projectRoot === undefined) {\n throw new Error('PROJECT_ROOT environment variable is required');\n}\n\nawait runMCPServer({\n dataDir: process.env['DATA_DIR'],\n config: process.env['CONFIG_PATH'],\n projectRoot,\n});\n"],"mappings":";;;AAWA,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAI9B,IAAM,aAAa,cAAc,YAAY,GAAG;AAChD,IAAM,YAAY,QAAQ,UAAU;AACpC,IAAM,aAAa,KAAK,WAAW,MAAM,IAAI;AAG7C,IAAI,CAAC,WAAW,KAAK,YAAY,cAAc,CAAC,GAAG;AACjD,QAAM,UAAU,MAAe;AAC7B,QAAI;AACF,eAAS,aAAa,EAAE,OAAO,SAAS,CAAC;AACzC,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,GAAG;AAEH,QAAM,MAAM,SAAS,kCAAkC;AACvD,WAAS,KAAK,EAAE,KAAK,YAAY,OAAO,UAAU,CAAC;AACrD;AAIA,IAAM,EAAE,aAAa,IAAI,MAAM,OAAO,aAAa;AAEnD,IAAM,cAAc,QAAQ,IAAI,cAAc;AAC9C,IAAI,gBAAgB,QAAW;AAC7B,QAAM,IAAI,MAAM,+CAA+C;AACjE;AAEA,MAAM,aAAa;AAAA,EACjB,SAAS,QAAQ,IAAI,UAAU;AAAA,EAC/B,QAAQ,QAAQ,IAAI,aAAa;AAAA,EACjC;AACF,CAAC;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bluera-knowledge",
3
- "version": "0.15.3",
3
+ "version": "0.15.4",
4
4
  "description": "CLI tool for managing knowledge stores with semantic search",
5
5
  "type": "module",
6
6
  "bin": {
@@ -70,7 +70,6 @@
70
70
  "hooks/",
71
71
  "commands/",
72
72
  "skills/",
73
- "scripts/",
74
73
  ".claude-plugin/",
75
74
  "README.md",
76
75
  "CHANGELOG.md",
@@ -1,24 +0,0 @@
1
- #!/bin/bash
2
- # MCP Server wrapper - ensures dependencies are installed before starting
3
- #
4
- # Claude Code installs plugins via git clone without npm install.
5
- # MCP servers start before SessionStart hooks fire, so we must install
6
- # dependencies here before starting the server.
7
-
8
- # Resolve absolute path to plugin root
9
- # BASH_SOURCE[0] is more reliable than $0 for sourced scripts
10
- # cd + pwd ensures we get an absolute path even if invoked with relative path
11
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
- PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$SCRIPT_DIR")}"
13
-
14
- # Install dependencies if missing
15
- if [ ! -d "$PLUGIN_ROOT/node_modules" ]; then
16
- if command -v bun &> /dev/null; then
17
- (cd "$PLUGIN_ROOT" && bun install --frozen-lockfile) >&2
18
- elif command -v npm &> /dev/null; then
19
- (cd "$PLUGIN_ROOT" && npm ci --silent) >&2
20
- fi
21
- fi
22
-
23
- # Start MCP server
24
- exec node "$PLUGIN_ROOT/dist/mcp/server.js"
@@ -1,260 +0,0 @@
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();
@@ -1,388 +0,0 @@
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" 2>&1 | tee -a "$LOG_FILE"; then
72
- pass "$name"
73
- return 0
74
- else
75
- fail "$name (exit code: ${PIPESTATUS[0]})"
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
- # Capture output while also showing it on terminal via tee
90
- if output=$(eval "$cmd" 2>&1 | tee -a "$LOG_FILE"); then
91
- if echo "$output" | grep -q "$expected"; then
92
- pass "$name"
93
- return 0
94
- else
95
- fail "$name (output missing: $expected)"
96
- return 1
97
- fi
98
- else
99
- fail "$name (command failed)"
100
- return 1
101
- fi
102
- }
103
-
104
- # Cleanup function
105
- cleanup() {
106
- log_header "Cleanup"
107
-
108
- # Delete test store if it exists
109
- log "Deleting test store: $TEST_STORE"
110
- bluera-knowledge store delete "$TEST_STORE" --force -d "$DATA_DIR" 2>/dev/null || true
111
-
112
- # Remove test folder
113
- log "Removing test folder: $TEST_FOLDER"
114
- rm -rf "$TEST_FOLDER"
115
-
116
- # Remove data directory
117
- log "Removing data directory: $DATA_DIR"
118
- rm -rf "$DATA_DIR"
119
-
120
- log "Cleanup complete"
121
- }
122
-
123
- # Set trap for cleanup on exit
124
- trap cleanup EXIT
125
-
126
- # Start validation
127
- log_header "NPM Module Validation - $TIMESTAMP"
128
- log "Log file: $LOG_FILE"
129
- log "Test store: $TEST_STORE"
130
- log "Test folder: $TEST_FOLDER"
131
- log "Data directory: $DATA_DIR"
132
-
133
- # Get expected version from package.json
134
- EXPECTED_VERSION=$(node -p "require('$REPO_ROOT/package.json').version")
135
- log "Expected version: $EXPECTED_VERSION"
136
-
137
- # Install latest from npm
138
- log_header "Installing Latest from npm"
139
- log "Installing bluera-knowledge@latest globally..."
140
- if npm install -g bluera-knowledge@latest >> "$LOG_FILE" 2>&1; then
141
- pass "npm install -g bluera-knowledge@latest"
142
- else
143
- fail "npm install -g bluera-knowledge@latest"
144
- log "ERROR: Failed to install package. Aborting."
145
- exit 1
146
- fi
147
-
148
- # Verify installation
149
- log_header "Verifying Installation"
150
-
151
- # Capture and display the installed version
152
- INSTALLED_VERSION=$(bluera-knowledge --version 2>&1 | head -1)
153
- log "Installed version: $INSTALLED_VERSION"
154
- VERSION_TEXT="Installed: bluera-knowledge@$INSTALLED_VERSION"
155
- VERSION_LEN=${#VERSION_TEXT}
156
- PADDING=$((VERSION_LEN + 4))
157
- echo ""
158
- printf " ╔"; printf '═%.0s' $(seq 1 $PADDING); printf "╗\n"
159
- printf " ║ %s ║\n" "$VERSION_TEXT"
160
- printf " ╚"; printf '═%.0s' $(seq 1 $PADDING); printf "╝\n"
161
- echo ""
162
-
163
- if [ "$INSTALLED_VERSION" != "$EXPECTED_VERSION" ]; then
164
- log "WARNING: Version mismatch! Expected $EXPECTED_VERSION but got $INSTALLED_VERSION"
165
- log "This may indicate npm cache issues. Try: npm cache clean --force"
166
- fi
167
-
168
- run_test_contains "bluera-knowledge --version" "$INSTALLED_VERSION" "bluera-knowledge --version"
169
-
170
- run_test_contains "bluera-knowledge --help" "CLI tool for managing knowledge stores" "bluera-knowledge --help"
171
-
172
- # Test stores list (should work even if empty)
173
- log_header "Testing Store Operations"
174
-
175
- run_test "bluera-knowledge stores (initial list)" "bluera-knowledge stores -d '$DATA_DIR' -f json"
176
-
177
- # Create a store via add-folder
178
- log_header "Testing add-folder"
179
-
180
- run_test "bluera-knowledge add-folder" "bluera-knowledge add-folder '$TEST_FOLDER' --name '$TEST_STORE' -d '$DATA_DIR'"
181
-
182
- # Verify store was created
183
- run_test_contains "Store appears in list" "$TEST_STORE" "bluera-knowledge stores -d '$DATA_DIR'"
184
-
185
- # Test add-repo
186
- log_header "Testing add-repo"
187
- REPO_STORE="npm-validation-repo-$TIMESTAMP"
188
- # Use a small, stable repo (sindresorhus/is - tiny, well-maintained)
189
- run_test "bluera-knowledge add-repo" "bluera-knowledge add-repo 'https://github.com/sindresorhus/is' --name '$REPO_STORE' -d '$DATA_DIR'"
190
- run_test_contains "Repo store appears in list" "$REPO_STORE" "bluera-knowledge stores -d '$DATA_DIR'"
191
- bluera-knowledge store delete "$REPO_STORE" --force -d "$DATA_DIR" 2>/dev/null || true
192
-
193
- # Test store info
194
- log_header "Testing store info"
195
-
196
- run_test_contains "bluera-knowledge store info" "$TEST_STORE" "bluera-knowledge store info '$TEST_STORE' -d '$DATA_DIR'"
197
-
198
- # Test store create (manual store creation)
199
- log_header "Testing store create"
200
- MANUAL_STORE="npm-validation-manual-$TIMESTAMP"
201
- run_test "bluera-knowledge store create" "bluera-knowledge store create '$MANUAL_STORE' -t file -s '$TEST_FOLDER' -d '$DATA_DIR'"
202
- run_test_contains "Manual store appears in list" "$MANUAL_STORE" "bluera-knowledge stores -d '$DATA_DIR'"
203
- bluera-knowledge store delete "$MANUAL_STORE" --force -d "$DATA_DIR" 2>/dev/null || true
204
-
205
- # Test search (may return no results, but should not error)
206
- log_header "Testing search"
207
-
208
- run_test "bluera-knowledge search" "bluera-knowledge search 'test content' --stores '$TEST_STORE' -d '$DATA_DIR' -f json"
209
-
210
- # Test index command (re-index)
211
- log_header "Testing index"
212
-
213
- run_test "bluera-knowledge index" "bluera-knowledge index '$TEST_STORE' -d '$DATA_DIR'"
214
-
215
- # Test search again after re-indexing
216
- run_test "bluera-knowledge search (after reindex)" "bluera-knowledge search 'validation' --stores '$TEST_STORE' -d '$DATA_DIR' -f json"
217
-
218
- # Test store delete
219
- log_header "Testing store delete"
220
-
221
- run_test "bluera-knowledge store delete" "bluera-knowledge store delete '$TEST_STORE' --force -d '$DATA_DIR'"
222
-
223
- # Verify store was deleted
224
- log "Verifying store was deleted..."
225
- if bluera-knowledge stores -d "$DATA_DIR" -f json 2>&1 | grep -q "$TEST_STORE"; then
226
- fail "Store still exists after delete"
227
- else
228
- pass "Store successfully deleted"
229
- fi
230
-
231
- # Test suggest command
232
- log_header "Testing suggest"
233
-
234
- # Create a minimal package.json for suggest to find
235
- echo '{"dependencies": {"lodash": "^4.0.0"}}' > "$TEST_FOLDER/package.json"
236
- run_test "bluera-knowledge suggest" "bluera-knowledge suggest -p '$TEST_FOLDER' -d '$DATA_DIR'"
237
-
238
- # Test sync command (should work with no definitions - returns empty result)
239
- log_header "Testing sync"
240
-
241
- run_test_contains "bluera-knowledge sync (no definitions)" "Sync completed" "bluera-knowledge sync -p '$TEST_FOLDER' -d '$DATA_DIR'"
242
-
243
- # Test sync with a definitions file
244
- mkdir -p "$TEST_FOLDER/.bluera/bluera-knowledge"
245
- cat > "$TEST_FOLDER/.bluera/bluera-knowledge/stores.config.json" << EOF
246
- {
247
- "version": 1,
248
- "stores": [
249
- {
250
- "name": "sync-test-store",
251
- "type": "file",
252
- "path": "."
253
- }
254
- ]
255
- }
256
- EOF
257
- run_test "bluera-knowledge sync (with definitions)" "bluera-knowledge sync -p '$TEST_FOLDER' -d '$DATA_DIR'"
258
-
259
- # Verify sync created the store
260
- run_test_contains "Sync created store" "sync-test-store" "bluera-knowledge stores -d '$DATA_DIR'"
261
-
262
- # Clean up sync test store
263
- bluera-knowledge store delete "sync-test-store" --force -d "$DATA_DIR" 2>/dev/null || true
264
-
265
- # Test crawl (simple mode - no Claude CLI required)
266
- log_header "Testing crawl (simple mode)"
267
- CRAWL_SIMPLE_STORE="npm-validation-crawl-simple-$TIMESTAMP"
268
- # Create web store first
269
- run_test "bluera-knowledge store create (web, simple)" "bluera-knowledge store create '$CRAWL_SIMPLE_STORE' -t web -s 'https://code.claude.com' -d '$DATA_DIR'"
270
- # Crawl using --simple mode (BFS, no Claude CLI)
271
- run_test "bluera-knowledge crawl --simple" "bluera-knowledge crawl 'https://code.claude.com/docs/' '$CRAWL_SIMPLE_STORE' --simple --max-pages 3 --fast -d '$DATA_DIR'"
272
- # Verify crawl indexed content
273
- run_test_contains "Simple crawl indexed content" "Claude" "bluera-knowledge search 'Claude Code' --stores '$CRAWL_SIMPLE_STORE' -d '$DATA_DIR' --include-content"
274
- bluera-knowledge store delete "$CRAWL_SIMPLE_STORE" --force -d "$DATA_DIR" 2>/dev/null || true
275
-
276
- # Test crawl (intelligent mode - requires Claude CLI)
277
- # NOTE: This test is non-blocking because Claude CLI response times vary.
278
- # Timeouts are expected and don't indicate a bug in bluera-knowledge.
279
- log_header "Testing crawl (intelligent mode)"
280
- CRAWL_STORE="npm-validation-crawl-$TIMESTAMP"
281
- # Check if Claude CLI is available
282
- if command -v claude >/dev/null 2>&1 || [ -f "$HOME/.claude/local/claude" ] || [ -f "$HOME/.local/bin/claude" ]; then
283
- # Create web store first
284
- run_test "bluera-knowledge store create (web)" "bluera-knowledge store create '$CRAWL_STORE' -t web -s 'https://code.claude.com' -d '$DATA_DIR'"
285
- # Crawl Claude Code docs with intelligent mode (non-blocking - may timeout)
286
- if bluera-knowledge crawl 'https://code.claude.com/docs/' "$CRAWL_STORE" --crawl 'first 3 documentation links' --max-pages 3 -d "$DATA_DIR" 2>&1; then
287
- pass "bluera-knowledge crawl (intelligent)"
288
- # Verify crawl indexed content (should find Claude Code content)
289
- run_test_contains "Intelligent crawl indexed content" "Claude" "bluera-knowledge search 'Claude Code documentation' --stores '$CRAWL_STORE' -d '$DATA_DIR' --include-content"
290
- else
291
- log "WARN: Intelligent crawl timed out (Claude CLI slow) - this is expected occasionally"
292
- pass "bluera-knowledge crawl (intelligent) - timeout acceptable"
293
- fi
294
- bluera-knowledge store delete "$CRAWL_STORE" --force -d "$DATA_DIR" 2>/dev/null || true
295
- else
296
- log "SKIP: Intelligent crawl test (Claude CLI not available)"
297
- fi
298
-
299
- # Test setup repos (list only - don't actually clone all repos)
300
- log_header "Testing setup repos"
301
- run_test_contains "bluera-knowledge setup repos --list" "claude-code" "bluera-knowledge setup repos --list"
302
-
303
- # Test index watch (start, verify running, stop)
304
- log_header "Testing index watch"
305
- # Create a store to watch
306
- WATCH_STORE="npm-validation-watch-$TIMESTAMP"
307
- bluera-knowledge add-folder "$TEST_FOLDER" --name "$WATCH_STORE" -d "$DATA_DIR" 2>/dev/null || true
308
-
309
- # Start watch in background
310
- bluera-knowledge index watch "$WATCH_STORE" -d "$DATA_DIR" &
311
- WATCH_PID=$!
312
- sleep 2
313
-
314
- if kill -0 $WATCH_PID 2>/dev/null; then
315
- pass "bluera-knowledge index watch (starts and runs)"
316
- kill $WATCH_PID 2>/dev/null || true
317
- wait $WATCH_PID 2>/dev/null || true
318
- else
319
- fail "bluera-knowledge index watch (failed to start)"
320
- fi
321
-
322
- bluera-knowledge store delete "$WATCH_STORE" --force -d "$DATA_DIR" 2>/dev/null || true
323
-
324
- # Test serve command (start, test, stop)
325
- log_header "Testing serve"
326
-
327
- SERVE_PORT=19876
328
- SERVE_PID=""
329
-
330
- # Start server in background
331
- log "Starting serve on port $SERVE_PORT..."
332
- bluera-knowledge serve --port $SERVE_PORT -d "$DATA_DIR" &
333
- SERVE_PID=$!
334
-
335
- # Give it time to start
336
- sleep 2
337
-
338
- # Check if server is running
339
- if kill -0 $SERVE_PID 2>/dev/null; then
340
- log "Server started with PID $SERVE_PID"
341
-
342
- # Test health endpoint
343
- if curl -s "http://localhost:$SERVE_PORT/health" | grep -q "ok"; then
344
- pass "bluera-knowledge serve (health endpoint)"
345
- else
346
- fail "bluera-knowledge serve (health endpoint not responding)"
347
- fi
348
-
349
- # Stop server
350
- kill $SERVE_PID 2>/dev/null || true
351
- wait $SERVE_PID 2>/dev/null || true
352
- log "Server stopped"
353
- else
354
- fail "bluera-knowledge serve (failed to start)"
355
- fi
356
-
357
- # Test mcp command (just verify it starts and outputs JSON-RPC)
358
- log_header "Testing mcp"
359
-
360
- MCP_OUTPUT=$(timeout 2 bluera-knowledge mcp -d "$DATA_DIR" 2>&1 || true)
361
- if echo "$MCP_OUTPUT" | grep -qE "(jsonrpc|ready|listening|MCP)" 2>/dev/null || [ $? -eq 124 ]; then
362
- # Timeout (124) is expected - MCP keeps running until killed
363
- pass "bluera-knowledge mcp (starts without error)"
364
- else
365
- # Even if it times out or produces no output, as long as it didn't crash
366
- if [ -z "$MCP_OUTPUT" ]; then
367
- pass "bluera-knowledge mcp (starts without error)"
368
- else
369
- log "MCP output: $MCP_OUTPUT"
370
- fail "bluera-knowledge mcp (unexpected output)"
371
- fi
372
- fi
373
-
374
- # Summary
375
- log_header "Validation Summary"
376
- log "Tests run: $TESTS_RUN"
377
- log "Tests passed: $TESTS_PASSED"
378
- log "Tests failed: $TESTS_FAILED"
379
- log ""
380
- log "Log file: $LOG_FILE"
381
-
382
- if [ "$TESTS_FAILED" -gt 0 ]; then
383
- log "VALIDATION FAILED"
384
- exit 1
385
- else
386
- log "VALIDATION PASSED"
387
- exit 0
388
- fi