bluera-knowledge 0.30.0 → 0.32.0
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-plugin/plugin.json +24 -0
- package/.mcp.json +13 -0
- package/CHANGELOG.md +37 -0
- package/NOTICE +47 -0
- package/README.md +2 -2
- package/bun.lock +1978 -0
- package/commands/add-folder.md +48 -0
- package/commands/add-repo.md +50 -0
- package/commands/cancel.md +63 -0
- package/commands/check-status.md +130 -0
- package/commands/crawl.md +61 -0
- package/commands/doctor.md +27 -0
- package/commands/eval.md +222 -0
- package/commands/health.md +72 -0
- package/commands/index.md +48 -0
- package/commands/remove-store.md +52 -0
- package/commands/search.md +80 -0
- package/commands/search.sh +63 -0
- package/commands/skill-activation.md +131 -0
- package/commands/stores.md +54 -0
- package/commands/suggest.md +118 -0
- package/commands/sync.md +96 -0
- package/commands/test-plugin.md +547 -0
- package/commands/uninstall.md +65 -0
- package/dist/{chunk-B335UOU7.js → chunk-3TB7TDVF.js} +24 -3
- package/dist/chunk-3TB7TDVF.js.map +1 -0
- package/dist/{chunk-KCI4U6FH.js → chunk-KDZDLJUY.js} +2 -2
- package/dist/{chunk-AEXFPA57.js → chunk-YDTTD53Y.js} +158 -26
- package/dist/chunk-YDTTD53Y.js.map +1 -0
- package/dist/index.js +3 -3
- package/dist/mcp/bootstrap.js +10 -0
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/mcp/server.d.ts +5 -3
- package/dist/mcp/server.js +2 -2
- package/dist/workers/background-worker-cli.js +2 -2
- package/hooks/check-ready.sh +109 -0
- package/hooks/hooks.json +87 -0
- package/hooks/job-status-hook.sh +51 -0
- package/hooks/posttooluse-bk-reminder.py +126 -0
- package/hooks/posttooluse-web-research.py +209 -0
- package/hooks/pretooluse-bk-suggest.py +296 -0
- package/hooks/skill-activation.py +221 -0
- package/hooks/skill-rules.json +131 -0
- package/package.json +10 -2
- package/scripts/CLAUDE.md +65 -0
- package/scripts/auto-setup.sh +65 -0
- package/scripts/bench-regression.sh +345 -0
- package/scripts/dev.sh +16 -0
- package/scripts/doctor.sh +103 -0
- package/scripts/download-models.ts +188 -0
- package/scripts/export-web-store.ts +142 -0
- package/scripts/lib/mock-server.sh +70 -0
- package/scripts/mcp-wrapper.sh +91 -0
- package/scripts/setup.sh +224 -0
- package/scripts/test-mcp-dev.js +260 -0
- package/scripts/validate-local.sh +412 -0
- package/scripts/validate-npm-release.sh +406 -0
- package/skills/advanced-workflows/SKILL.md +273 -0
- package/skills/knowledge-search/SKILL.md +110 -0
- package/skills/search-optimization/SKILL.md +199 -0
- package/skills/search-optimization/references/mistakes.md +21 -0
- package/skills/search-optimization/references/strategies.md +80 -0
- package/skills/store-lifecycle/SKILL.md +470 -0
- package/skills/when-to-query/SKILL.md +160 -0
- package/dist/chunk-AEXFPA57.js.map +0 -1
- package/dist/chunk-B335UOU7.js.map +0 -1
- /package/dist/{chunk-KCI4U6FH.js.map → chunk-KDZDLJUY.js.map} +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Scripts Directory
|
|
2
|
+
|
|
3
|
+
Shell scripts for plugin setup, diagnostics, and MCP server.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## MCP Wrapper (`mcp-wrapper.sh`)
|
|
8
|
+
|
|
9
|
+
Entry point for MCP server. Called by Claude Code when starting the plugin.
|
|
10
|
+
|
|
11
|
+
**CRITICAL:** Uses `sort -V -r` to get LATEST cached version, not first alphabetically.
|
|
12
|
+
- Bug: Alphabetical sort put 0.20.0 before 0.22.x
|
|
13
|
+
- Fix: Version sort ensures latest cached version with all fixes
|
|
14
|
+
|
|
15
|
+
**Flow:**
|
|
16
|
+
1. Check `installed_plugins.json` for explicit path
|
|
17
|
+
2. Fallback: Scan cache dirs, sort by version (descending), use first valid
|
|
18
|
+
3. Run `bootstrap.js` which handles deps and starts MCP
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Setup (`setup.sh`)
|
|
23
|
+
|
|
24
|
+
Manual and auto setup script. Installs dependencies.
|
|
25
|
+
|
|
26
|
+
**CRITICAL:** Uses `npm install --legacy-peer-deps`
|
|
27
|
+
- Required because tree-sitter-go@0.25 and tree-sitter-rust@0.24 have incompatible peer deps
|
|
28
|
+
- Without this flag, npm fails even though tree-sitter is optional
|
|
29
|
+
|
|
30
|
+
**Installs:**
|
|
31
|
+
- MCP wrapper to `~/.local/bin/bluera-knowledge-mcp`
|
|
32
|
+
- node_modules (bun or npm)
|
|
33
|
+
- Playwright Chromium browser
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Auto-Setup (`auto-setup.sh`)
|
|
38
|
+
|
|
39
|
+
Runs on SessionStart (async, non-blocking). Calls setup.sh if needed.
|
|
40
|
+
|
|
41
|
+
**Fast exit:** If node_modules AND wrapper exist, exits immediately.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Doctor (`doctor.sh`)
|
|
46
|
+
|
|
47
|
+
Diagnostic tool for MCP failures. **Use this first when MCP breaks.**
|
|
48
|
+
|
|
49
|
+
Invoked via: `/bluera-knowledge:doctor`
|
|
50
|
+
|
|
51
|
+
**Checks:**
|
|
52
|
+
- Build tools (make) - REQUIRED for native modules
|
|
53
|
+
- Node.js version - WARNS on v24+ (native module issues)
|
|
54
|
+
- node_modules - installation status
|
|
55
|
+
- MCP wrapper - installation status
|
|
56
|
+
- Python 3 - optional, for embeddings
|
|
57
|
+
- Playwright - optional, for web crawling
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Check-Ready (`check-ready.sh`)
|
|
62
|
+
|
|
63
|
+
Fast validation on SessionStart (sync, blocking with timeout).
|
|
64
|
+
|
|
65
|
+
Verifies prerequisites without full setup. Exits 2 for blocking errors.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Bluera Knowledge Plugin - Auto Setup
|
|
3
|
+
# Runs on: SessionStart (async) - automatically sets up plugin if needed
|
|
4
|
+
#
|
|
5
|
+
# This script runs in the background on every session start.
|
|
6
|
+
# It exits quickly (0) if already set up, or runs full setup if needed.
|
|
7
|
+
# Non-interactive: cannot prompt for user input (no TTY).
|
|
8
|
+
|
|
9
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$0")")}"
|
|
10
|
+
|
|
11
|
+
# Colors for output
|
|
12
|
+
GREEN='\033[0;32m'
|
|
13
|
+
YELLOW='\033[1;33m'
|
|
14
|
+
NC='\033[0m'
|
|
15
|
+
|
|
16
|
+
# Debug logging
|
|
17
|
+
LOG_DIR="${PROJECT_ROOT:-.}/.bluera/bluera-knowledge/logs"
|
|
18
|
+
LOG_FILE="$LOG_DIR/app.log"
|
|
19
|
+
|
|
20
|
+
log_debug() {
|
|
21
|
+
local msg="$1"
|
|
22
|
+
mkdir -p "$LOG_DIR" 2>/dev/null || true
|
|
23
|
+
local timestamp
|
|
24
|
+
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
25
|
+
echo "{\"time\":\"$timestamp\",\"level\":\"debug\",\"module\":\"auto-setup.sh\",\"msg\":\"$msg\"}" >> "$LOG_FILE" 2>/dev/null || true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
log_debug "Auto-setup starting, PLUGIN_ROOT=$PLUGIN_ROOT"
|
|
29
|
+
|
|
30
|
+
# Fast exit if already set up
|
|
31
|
+
WRAPPER_PATH="$HOME/.local/bin/bluera-knowledge-mcp"
|
|
32
|
+
if [ -d "$PLUGIN_ROOT/node_modules" ] && [ -f "$WRAPPER_PATH" ]; then
|
|
33
|
+
log_debug "Already set up (node_modules and wrapper exist), exiting"
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
log_debug "Setup needed - node_modules: $([ -d "$PLUGIN_ROOT/node_modules" ] && echo 'exists' || echo 'missing'), wrapper: $([ -f "$WRAPPER_PATH" ] && echo 'exists' || echo 'missing')"
|
|
38
|
+
|
|
39
|
+
# Check for build tools - if missing, print instructions and exit with error
|
|
40
|
+
# Cannot auto-install because that requires sudo (interactive)
|
|
41
|
+
if ! command -v make &>/dev/null; then
|
|
42
|
+
log_debug "Build tools (make) not found, printing instructions"
|
|
43
|
+
echo "[bluera-knowledge] ERROR: Build tools (make) not found - required for native modules." >&2
|
|
44
|
+
echo "" >&2
|
|
45
|
+
echo "Install build tools, then restart Claude Code:" >&2
|
|
46
|
+
echo " Debian/Ubuntu: sudo apt install build-essential" >&2
|
|
47
|
+
echo " Fedora/RHEL: sudo dnf groupinstall 'Development Tools'" >&2
|
|
48
|
+
echo " macOS: xcode-select --install" >&2
|
|
49
|
+
# Exit 2 = blocking error, stderr shown to user
|
|
50
|
+
exit 2
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Run setup non-interactively
|
|
54
|
+
log_debug "Running setup.sh with NONINTERACTIVE=1"
|
|
55
|
+
echo -e "${YELLOW}[bluera-knowledge] Running first-time setup (this may take a moment)...${NC}"
|
|
56
|
+
|
|
57
|
+
export NONINTERACTIVE=1
|
|
58
|
+
if "$PLUGIN_ROOT/scripts/setup.sh"; then
|
|
59
|
+
log_debug "Setup completed successfully"
|
|
60
|
+
echo -e "${GREEN}[bluera-knowledge] Setup complete ✓${NC}"
|
|
61
|
+
echo -e "${GREEN}[bluera-knowledge] Restart Claude Code to enable MCP server.${NC}"
|
|
62
|
+
else
|
|
63
|
+
log_debug "Setup failed"
|
|
64
|
+
echo -e "${YELLOW}[bluera-knowledge] Setup failed. Run manually: $PLUGIN_ROOT/scripts/setup.sh${NC}"
|
|
65
|
+
fi
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# bench-regression.sh — Hard merge gate for model changes
|
|
5
|
+
#
|
|
6
|
+
# Runs candidate vs champion on real-v1-test (3x alternating),
|
|
7
|
+
# writes comparison.json, exits non-zero on failure.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# BK_FINETUNED_MODEL=./models/bge-small-finetuned-onnx scripts/bench-regression.sh
|
|
11
|
+
#
|
|
12
|
+
# Requires: jq, bun, benchmarks/search/baselines/champion-mean-v1-test.json
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
16
|
+
cd "$PROJECT_ROOT"
|
|
17
|
+
|
|
18
|
+
CHAMPION_BASELINE="benchmarks/search/baselines/champion-mean-v1-test.json"
|
|
19
|
+
SPLIT_FILE="benchmarks/search/splits/test-cases.json"
|
|
20
|
+
TIMESTAMP="$(date -u +%Y%m%dT%H%M%S)"
|
|
21
|
+
RESULTS_DIR="benchmarks/search/regression-results/${TIMESTAMP}"
|
|
22
|
+
|
|
23
|
+
# Validate prerequisites
|
|
24
|
+
if [ ! -f "$CHAMPION_BASELINE" ]; then
|
|
25
|
+
echo "ERROR: Champion baseline not found: $CHAMPION_BASELINE"
|
|
26
|
+
echo "Run Step 8A first."
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
if [ ! -f "$SPLIT_FILE" ]; then
|
|
31
|
+
echo "ERROR: Test split not found: $SPLIT_FILE"
|
|
32
|
+
echo "Run split-dataset.ts first."
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
mkdir -p "$RESULTS_DIR"
|
|
37
|
+
|
|
38
|
+
echo "============================================================"
|
|
39
|
+
echo "REGRESSION GATE — bench-regression.sh"
|
|
40
|
+
echo "============================================================"
|
|
41
|
+
echo "Timestamp: $TIMESTAMP"
|
|
42
|
+
echo "Results: $RESULTS_DIR"
|
|
43
|
+
echo "Champion: $CHAMPION_BASELINE"
|
|
44
|
+
echo ""
|
|
45
|
+
|
|
46
|
+
# --- Run 3 alternating champion/candidate pairs ---
|
|
47
|
+
|
|
48
|
+
run_champion() {
|
|
49
|
+
local run_num=$1
|
|
50
|
+
local artifact="$RESULTS_DIR/champion-run-${run_num}.json"
|
|
51
|
+
echo "[Run ${run_num}/3] Champion (baseline)..."
|
|
52
|
+
(
|
|
53
|
+
unset BK_FINETUNED_MODEL BK_QUERY_PREFIX
|
|
54
|
+
bun run bench:search --dataset real-v1-test --reuse-index \
|
|
55
|
+
--artifacts "$artifact" 2>&1 | grep -E "Hit@1|SUMMARY|Artifact"
|
|
56
|
+
)
|
|
57
|
+
echo " -> $artifact"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
run_candidate() {
|
|
61
|
+
local run_num=$1
|
|
62
|
+
local artifact="$RESULTS_DIR/candidate-run-${run_num}.json"
|
|
63
|
+
echo "[Run ${run_num}/3] Candidate (finetuned)..."
|
|
64
|
+
# Finetuned BGE models still need the BGE query prefix
|
|
65
|
+
BK_QUERY_PREFIX='Represent this sentence for searching relevant passages: ' \
|
|
66
|
+
bun run bench:search --dataset real-v1-test --setup --force \
|
|
67
|
+
--artifacts "$artifact" 2>&1 | grep -E "Hit@1|SUMMARY|Artifact"
|
|
68
|
+
echo " -> $artifact"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# Alternating: C1 -> X1 -> C2 -> X2 -> C3 -> X3
|
|
72
|
+
for i in 1 2 3; do
|
|
73
|
+
run_champion "$i"
|
|
74
|
+
run_candidate "$i"
|
|
75
|
+
done
|
|
76
|
+
|
|
77
|
+
echo ""
|
|
78
|
+
echo "All 6 runs complete. Analyzing..."
|
|
79
|
+
echo ""
|
|
80
|
+
|
|
81
|
+
# --- Fingerprint parity check ---
|
|
82
|
+
|
|
83
|
+
echo "Fingerprint parity check..."
|
|
84
|
+
CHAMPION_FP=$(jq -S '.configFingerprint | del(.embedding.model, .embedding.modelSha256)' "$RESULTS_DIR/champion-run-1.json")
|
|
85
|
+
CANDIDATE_FP=$(jq -S '.configFingerprint | del(.embedding.model, .embedding.modelSha256)' "$RESULTS_DIR/candidate-run-1.json")
|
|
86
|
+
|
|
87
|
+
if [ "$CHAMPION_FP" != "$CANDIDATE_FP" ]; then
|
|
88
|
+
echo "FAIL: Fingerprint mismatch (non-model fields differ)"
|
|
89
|
+
diff <(echo "$CHAMPION_FP") <(echo "$CANDIDATE_FP") || true
|
|
90
|
+
echo ""
|
|
91
|
+
echo "Only embedding.model and embedding.modelSha256 may differ."
|
|
92
|
+
# Write failed comparison
|
|
93
|
+
jq -n \
|
|
94
|
+
--arg splitHash "$(sha256sum "$SPLIT_FILE" | cut -d' ' -f1)" \
|
|
95
|
+
--arg gitSha "$(git rev-parse --short HEAD)" \
|
|
96
|
+
--arg verdict "FAIL: fingerprint mismatch" \
|
|
97
|
+
'{splitHash: $splitHash, gitSha: $gitSha, verdict: $verdict}' \
|
|
98
|
+
> "$RESULTS_DIR/comparison.json"
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
101
|
+
echo " PASS: all non-model fields identical"
|
|
102
|
+
|
|
103
|
+
# --- Compute integer hits ---
|
|
104
|
+
# Weighted integer hits = sum of weight for cases where hitAt1 == true
|
|
105
|
+
# Matches aggregation in metrics.ts:216
|
|
106
|
+
|
|
107
|
+
compute_hits() {
|
|
108
|
+
local artifact=$1
|
|
109
|
+
jq '[.results[] | select(.metrics.hitAt1 == true) | .weight // 1] | add // 0' "$artifact"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
echo ""
|
|
113
|
+
echo "Integer hit counts (weighted):"
|
|
114
|
+
|
|
115
|
+
CHAMPION_HITS=()
|
|
116
|
+
CANDIDATE_HITS=()
|
|
117
|
+
for i in 1 2 3; do
|
|
118
|
+
ch=$(compute_hits "$RESULTS_DIR/champion-run-${i}.json")
|
|
119
|
+
cx=$(compute_hits "$RESULTS_DIR/candidate-run-${i}.json")
|
|
120
|
+
CHAMPION_HITS+=("$ch")
|
|
121
|
+
CANDIDATE_HITS+=("$cx")
|
|
122
|
+
echo " Pair $i: champion=$ch, candidate=$cx (delta=$((cx - ch)))"
|
|
123
|
+
done
|
|
124
|
+
|
|
125
|
+
# --- Stability check ---
|
|
126
|
+
echo ""
|
|
127
|
+
echo "Stability check..."
|
|
128
|
+
if [ "${CHAMPION_HITS[0]}" != "${CHAMPION_HITS[1]}" ] || [ "${CHAMPION_HITS[0]}" != "${CHAMPION_HITS[2]}" ]; then
|
|
129
|
+
echo " WARNING: Champion hits vary across runs: ${CHAMPION_HITS[*]}"
|
|
130
|
+
fi
|
|
131
|
+
if [ "${CANDIDATE_HITS[0]}" != "${CANDIDATE_HITS[1]}" ] || [ "${CANDIDATE_HITS[0]}" != "${CANDIDATE_HITS[2]}" ]; then
|
|
132
|
+
echo " WARNING: Candidate hits vary across runs: ${CANDIDATE_HITS[*]}"
|
|
133
|
+
fi
|
|
134
|
+
STABLE="true"
|
|
135
|
+
if [ "${CANDIDATE_HITS[0]}" != "${CANDIDATE_HITS[1]}" ] || [ "${CANDIDATE_HITS[0]}" != "${CANDIDATE_HITS[2]}" ]; then
|
|
136
|
+
STABLE="false"
|
|
137
|
+
fi
|
|
138
|
+
echo " Candidate stable: $STABLE"
|
|
139
|
+
|
|
140
|
+
# --- Per-query win/loss (using run-1 as reference) ---
|
|
141
|
+
|
|
142
|
+
echo ""
|
|
143
|
+
echo "Per-query analysis (run 1)..."
|
|
144
|
+
|
|
145
|
+
# Extract hitAt1 per case ID for champion and candidate
|
|
146
|
+
CHAMPION_CASES=$(jq -r '[.results[] | {id, hit: .metrics.hitAt1, weight: (.weight // 1)}]' "$RESULTS_DIR/champion-run-1.json")
|
|
147
|
+
CANDIDATE_CASES=$(jq -r '[.results[] | {id, hit: .metrics.hitAt1, weight: (.weight // 1)}]' "$RESULTS_DIR/candidate-run-1.json")
|
|
148
|
+
|
|
149
|
+
WINS=0
|
|
150
|
+
LOSSES=0
|
|
151
|
+
TIES=0
|
|
152
|
+
|
|
153
|
+
# Compare per case
|
|
154
|
+
CASE_IDS=$(jq -r '.[].id' <<< "$CHAMPION_CASES")
|
|
155
|
+
while IFS= read -r case_id; do
|
|
156
|
+
ch_hit=$(jq -r --arg id "$case_id" '.[] | select(.id == $id) | .hit' <<< "$CHAMPION_CASES")
|
|
157
|
+
cx_hit=$(jq -r --arg id "$case_id" '.[] | select(.id == $id) | .hit' <<< "$CANDIDATE_CASES")
|
|
158
|
+
if [ "$ch_hit" = "false" ] && [ "$cx_hit" = "true" ]; then
|
|
159
|
+
((WINS++)) || true
|
|
160
|
+
elif [ "$ch_hit" = "true" ] && [ "$cx_hit" = "false" ]; then
|
|
161
|
+
((LOSSES++)) || true
|
|
162
|
+
else
|
|
163
|
+
((TIES++)) || true
|
|
164
|
+
fi
|
|
165
|
+
done <<< "$CASE_IDS"
|
|
166
|
+
|
|
167
|
+
echo " Wins: $WINS, Losses: $LOSSES, Ties: $TIES"
|
|
168
|
+
|
|
169
|
+
# --- Category analysis ---
|
|
170
|
+
|
|
171
|
+
echo ""
|
|
172
|
+
echo "Category analysis (run 1)..."
|
|
173
|
+
|
|
174
|
+
# Per-category integer hit deltas
|
|
175
|
+
CATEGORY_DELTAS=$(jq -n \
|
|
176
|
+
--argjson champion "$CHAMPION_CASES" \
|
|
177
|
+
--argjson candidate "$CANDIDATE_CASES" \
|
|
178
|
+
'[($champion | group_by(.id | split("-")[0:2] | join("-")) | .[] |
|
|
179
|
+
{category: (.[0].id | split("-")[0:2] | join("-")),
|
|
180
|
+
champion_hits: [.[] | select(.hit) | .weight // 1] | add // 0}) ] as $ch_cats |
|
|
181
|
+
[($candidate | group_by(.id | split("-")[0:2] | join("-")) | .[] |
|
|
182
|
+
{category: (.[0].id | split("-")[0:2] | join("-")),
|
|
183
|
+
candidate_hits: [.[] | select(.hit) | .weight // 1] | add // 0}) ] as $cx_cats |
|
|
184
|
+
[$ch_cats[] as $c | {category: $c.category,
|
|
185
|
+
champion: $c.champion_hits,
|
|
186
|
+
candidate: ([$cx_cats[] | select(.category == $c.category) | .candidate_hits][0] // 0),
|
|
187
|
+
delta: (([$cx_cats[] | select(.category == $c.category) | .candidate_hits][0] // 0) - $c.champion_hits)}]')
|
|
188
|
+
|
|
189
|
+
echo "$CATEGORY_DELTAS" | jq -r '.[] | " \(.category): champion=\(.champion) candidate=\(.candidate) delta=\(.delta)"'
|
|
190
|
+
|
|
191
|
+
# Check category guardrail: no category loses more than 1 weighted hit
|
|
192
|
+
CATEGORY_FAIL=$(echo "$CATEGORY_DELTAS" | jq '[.[] | select(.delta < -1)] | length')
|
|
193
|
+
|
|
194
|
+
# --- P95 latency ---
|
|
195
|
+
|
|
196
|
+
echo ""
|
|
197
|
+
echo "P95 latency:"
|
|
198
|
+
P95_DELTAS=()
|
|
199
|
+
for i in 1 2 3; do
|
|
200
|
+
ch_p95=$(jq '.summary.latency.p95' "$RESULTS_DIR/champion-run-${i}.json")
|
|
201
|
+
cx_p95=$(jq '.summary.latency.p95' "$RESULTS_DIR/candidate-run-${i}.json")
|
|
202
|
+
delta=$(echo "$cx_p95 - $ch_p95" | bc -l 2>/dev/null || echo "0")
|
|
203
|
+
P95_DELTAS+=("$delta")
|
|
204
|
+
printf " Pair %d: champion=%.1fms candidate=%.1fms delta=%.1fms\n" "$i" "$ch_p95" "$cx_p95" "$delta"
|
|
205
|
+
done
|
|
206
|
+
|
|
207
|
+
# Median P95 check (baseline + 20ms)
|
|
208
|
+
CHAMPION_P95_MEDIAN=$(for i in 1 2 3; do jq '.summary.latency.p95' "$RESULTS_DIR/champion-run-${i}.json"; done | sort -n | sed -n '2p')
|
|
209
|
+
CANDIDATE_P95_MEDIAN=$(for i in 1 2 3; do jq '.summary.latency.p95' "$RESULTS_DIR/candidate-run-${i}.json"; done | sort -n | sed -n '2p')
|
|
210
|
+
LATENCY_LIMIT=$(echo "$CHAMPION_P95_MEDIAN + 20" | bc -l)
|
|
211
|
+
LATENCY_OK=$(echo "$CANDIDATE_P95_MEDIAN <= $LATENCY_LIMIT" | bc -l)
|
|
212
|
+
|
|
213
|
+
printf " Median P95: champion=%.1fms candidate=%.1fms limit=%.1fms\n" "$CHAMPION_P95_MEDIAN" "$CANDIDATE_P95_MEDIAN" "$LATENCY_LIMIT"
|
|
214
|
+
|
|
215
|
+
# --- Gate decisions ---
|
|
216
|
+
|
|
217
|
+
echo ""
|
|
218
|
+
echo "============================================================"
|
|
219
|
+
echo "GATE EVALUATION"
|
|
220
|
+
echo "============================================================"
|
|
221
|
+
|
|
222
|
+
VERDICT="PASS"
|
|
223
|
+
REASONS=()
|
|
224
|
+
|
|
225
|
+
# Gate 1: Integer-hit gate (raised bar)
|
|
226
|
+
HIT_DELTA=$((CANDIDATE_HITS[0] - CHAMPION_HITS[0]))
|
|
227
|
+
echo "Gate 1 — Integer-hit gate: delta=$HIT_DELTA weighted hits"
|
|
228
|
+
|
|
229
|
+
GATE1_PASS="false"
|
|
230
|
+
if [ "$HIT_DELTA" -ge 2 ]; then
|
|
231
|
+
echo " PASS (path a): gain >= 2 weighted hits"
|
|
232
|
+
GATE1_PASS="true"
|
|
233
|
+
elif [ "$HIT_DELTA" -ge 1 ] && [ "$LOSSES" -eq 0 ]; then
|
|
234
|
+
echo " PASS (path b): gain >= 1 AND zero losses"
|
|
235
|
+
GATE1_PASS="true"
|
|
236
|
+
else
|
|
237
|
+
echo " FAIL: delta=$HIT_DELTA, losses=$LOSSES (need >=2 hits, or >=1 with 0 losses)"
|
|
238
|
+
VERDICT="FAIL"
|
|
239
|
+
REASONS+=("integer-hit gate: delta=$HIT_DELTA losses=$LOSSES")
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
# Gate 2: Category guardrail
|
|
243
|
+
echo "Gate 2 — Category guardrail: $CATEGORY_FAIL categories with >1 hit loss"
|
|
244
|
+
if [ "$CATEGORY_FAIL" -gt 0 ]; then
|
|
245
|
+
echo " FAIL: category regression detected"
|
|
246
|
+
VERDICT="FAIL"
|
|
247
|
+
REASONS+=("category guardrail: $CATEGORY_FAIL categories regressed >1 hit")
|
|
248
|
+
else
|
|
249
|
+
echo " PASS"
|
|
250
|
+
fi
|
|
251
|
+
|
|
252
|
+
# Gate 3: Per-query win/loss
|
|
253
|
+
echo "Gate 3 — Per-query: wins=$WINS losses=$LOSSES"
|
|
254
|
+
if [ "$WINS" -gt "$LOSSES" ]; then
|
|
255
|
+
echo " PASS"
|
|
256
|
+
else
|
|
257
|
+
echo " FAIL: wins must exceed losses"
|
|
258
|
+
VERDICT="FAIL"
|
|
259
|
+
REASONS+=("per-query: wins=$WINS <= losses=$LOSSES")
|
|
260
|
+
fi
|
|
261
|
+
|
|
262
|
+
# Gate 4: Latency
|
|
263
|
+
echo "Gate 4 — Latency: candidate median P95 within baseline + 20ms"
|
|
264
|
+
if [ "$LATENCY_OK" -eq 1 ]; then
|
|
265
|
+
echo " PASS"
|
|
266
|
+
else
|
|
267
|
+
echo " FAIL: candidate P95 exceeds limit"
|
|
268
|
+
VERDICT="FAIL"
|
|
269
|
+
REASONS+=("latency: candidate=$CANDIDATE_P95_MEDIAN > limit=$LATENCY_LIMIT")
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
# Gate 5: Stability
|
|
273
|
+
echo "Gate 5 — Stability: candidate hits identical across 3 runs"
|
|
274
|
+
if [ "$STABLE" = "true" ]; then
|
|
275
|
+
echo " PASS"
|
|
276
|
+
else
|
|
277
|
+
echo " FAIL: candidate hit count varies"
|
|
278
|
+
VERDICT="FAIL"
|
|
279
|
+
REASONS+=("stability: hits vary ${CANDIDATE_HITS[*]}")
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
# --- Build comparison.json ---
|
|
283
|
+
|
|
284
|
+
SPLIT_HASH=$(sha256sum "$SPLIT_FILE" 2>/dev/null | cut -d' ' -f1 || shasum -a 256 "$SPLIT_FILE" | cut -d' ' -f1)
|
|
285
|
+
GIT_SHA=$(git rev-parse --short HEAD)
|
|
286
|
+
|
|
287
|
+
# Model SHA (try ONNX model if BK_FINETUNED_MODEL is set)
|
|
288
|
+
MODEL_SHA="base-model"
|
|
289
|
+
if [ -n "${BK_FINETUNED_MODEL:-}" ] && [ -f "${BK_FINETUNED_MODEL}/onnx/model.onnx" ]; then
|
|
290
|
+
MODEL_SHA=$(sha256sum "${BK_FINETUNED_MODEL}/onnx/model.onnx" 2>/dev/null | cut -d' ' -f1 || shasum -a 256 "${BK_FINETUNED_MODEL}/onnx/model.onnx" | cut -d' ' -f1)
|
|
291
|
+
fi
|
|
292
|
+
|
|
293
|
+
# Training manifest (if exists)
|
|
294
|
+
TRAINING_MANIFEST="{}"
|
|
295
|
+
if [ -f "training/data/training-manifest.json" ]; then
|
|
296
|
+
TRAINING_MANIFEST=$(cat "training/data/training-manifest.json")
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
REASON_STR=""
|
|
300
|
+
if [ ${#REASONS[@]} -gt 0 ]; then
|
|
301
|
+
REASON_STR=$(printf '%s; ' "${REASONS[@]}")
|
|
302
|
+
fi
|
|
303
|
+
|
|
304
|
+
jq -n \
|
|
305
|
+
--arg splitHash "$SPLIT_HASH" \
|
|
306
|
+
--arg gitSha "$GIT_SHA" \
|
|
307
|
+
--arg modelSha "$MODEL_SHA" \
|
|
308
|
+
--argjson championFP "$(jq '.configFingerprint' "$RESULTS_DIR/champion-run-1.json")" \
|
|
309
|
+
--argjson candidateFP "$(jq '.configFingerprint' "$RESULTS_DIR/candidate-run-1.json")" \
|
|
310
|
+
--argjson championHits "[${CHAMPION_HITS[0]},${CHAMPION_HITS[1]},${CHAMPION_HITS[2]}]" \
|
|
311
|
+
--argjson candidateHits "[${CANDIDATE_HITS[0]},${CANDIDATE_HITS[1]},${CANDIDATE_HITS[2]}]" \
|
|
312
|
+
--argjson categoryDeltas "$CATEGORY_DELTAS" \
|
|
313
|
+
--argjson perQueryWinLoss "{\"wins\":$WINS,\"losses\":$LOSSES,\"ties\":$TIES}" \
|
|
314
|
+
--argjson trainingManifest "$TRAINING_MANIFEST" \
|
|
315
|
+
--arg verdict "$VERDICT" \
|
|
316
|
+
--arg reason "$REASON_STR" \
|
|
317
|
+
'{
|
|
318
|
+
splitHash: $splitHash,
|
|
319
|
+
gitSha: $gitSha,
|
|
320
|
+
modelSha: $modelSha,
|
|
321
|
+
championFingerprint: $championFP,
|
|
322
|
+
candidateFingerprint: $candidateFP,
|
|
323
|
+
integerHits: {champion: $championHits, candidate: $candidateHits},
|
|
324
|
+
categoryDeltas: $categoryDeltas,
|
|
325
|
+
perQueryWinLoss: $perQueryWinLoss,
|
|
326
|
+
p95Deltas: [],
|
|
327
|
+
trainingManifest: $trainingManifest,
|
|
328
|
+
verdict: $verdict,
|
|
329
|
+
reason: (if $reason == "" then null else $reason end)
|
|
330
|
+
}' > "$RESULTS_DIR/comparison.json"
|
|
331
|
+
|
|
332
|
+
echo ""
|
|
333
|
+
echo "============================================================"
|
|
334
|
+
echo "VERDICT: $VERDICT"
|
|
335
|
+
if [ -n "$REASON_STR" ]; then
|
|
336
|
+
echo "Reason: $REASON_STR"
|
|
337
|
+
fi
|
|
338
|
+
echo "Comparison: $RESULTS_DIR/comparison.json"
|
|
339
|
+
echo "============================================================"
|
|
340
|
+
|
|
341
|
+
if [ "$VERDICT" = "PASS" ]; then
|
|
342
|
+
exit 0
|
|
343
|
+
else
|
|
344
|
+
exit 1
|
|
345
|
+
fi
|
package/scripts/dev.sh
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Development script - runs Claude with this plugin loaded locally
|
|
3
|
+
#
|
|
4
|
+
# Usage: ./scripts/dev.sh [claude args...]
|
|
5
|
+
#
|
|
6
|
+
# This sets CLAUDE_PLUGIN_ROOT so the MCP server can find its files
|
|
7
|
+
# when running with --plugin-dir (which doesn't set this var automatically).
|
|
8
|
+
|
|
9
|
+
set -e
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
13
|
+
|
|
14
|
+
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
|
|
15
|
+
|
|
16
|
+
exec claude --plugin-dir "$PLUGIN_ROOT" "$@"
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Bluera Knowledge Doctor - Comprehensive Diagnostics
|
|
3
|
+
# Run with: /bluera-knowledge:doctor
|
|
4
|
+
|
|
5
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$0")")}"
|
|
6
|
+
|
|
7
|
+
echo "═══════════════════════════════════════════════════════════"
|
|
8
|
+
echo " Bluera Knowledge Doctor"
|
|
9
|
+
echo "═══════════════════════════════════════════════════════════"
|
|
10
|
+
echo ""
|
|
11
|
+
|
|
12
|
+
ISSUES=0
|
|
13
|
+
|
|
14
|
+
# Check 1: Build tools
|
|
15
|
+
echo "Checking build tools..."
|
|
16
|
+
if command -v make &>/dev/null; then
|
|
17
|
+
echo " [OK] make found"
|
|
18
|
+
else
|
|
19
|
+
echo " [FAIL] make NOT found - REQUIRED for native modules"
|
|
20
|
+
echo ""
|
|
21
|
+
echo " FIX: Install build tools:"
|
|
22
|
+
echo " Debian/Ubuntu: sudo apt install build-essential"
|
|
23
|
+
echo " Fedora/RHEL: sudo dnf groupinstall 'Development Tools'"
|
|
24
|
+
echo " macOS: xcode-select --install"
|
|
25
|
+
echo ""
|
|
26
|
+
ISSUES=$((ISSUES + 1))
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# Check 2: Node.js
|
|
30
|
+
echo "Checking Node.js..."
|
|
31
|
+
if command -v node &>/dev/null; then
|
|
32
|
+
NODE_VERSION=$(node --version)
|
|
33
|
+
NODE_MAJOR=$(echo "$NODE_VERSION" | cut -d. -f1 | tr -d 'v')
|
|
34
|
+
if [ "$NODE_MAJOR" -ge 24 ]; then
|
|
35
|
+
echo " [WARN] Node.js $NODE_VERSION - v24+ may have native module issues"
|
|
36
|
+
echo ""
|
|
37
|
+
echo " Native modules (tree-sitter, lancedb) may not compile on Node.js v24+"
|
|
38
|
+
echo " due to V8 API changes. Recommended: Use Node.js v20.x or v22.x (LTS)"
|
|
39
|
+
echo ""
|
|
40
|
+
else
|
|
41
|
+
echo " [OK] Node.js $NODE_VERSION"
|
|
42
|
+
fi
|
|
43
|
+
else
|
|
44
|
+
echo " [FAIL] Node.js NOT found"
|
|
45
|
+
ISSUES=$((ISSUES + 1))
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Check 3: node_modules
|
|
49
|
+
echo "Checking plugin dependencies..."
|
|
50
|
+
if [ -d "$PLUGIN_ROOT/node_modules" ]; then
|
|
51
|
+
echo " [OK] node_modules installed"
|
|
52
|
+
else
|
|
53
|
+
echo " [FAIL] node_modules missing"
|
|
54
|
+
echo ""
|
|
55
|
+
echo " FIX: Run setup manually:"
|
|
56
|
+
echo " $PLUGIN_ROOT/scripts/setup.sh"
|
|
57
|
+
echo ""
|
|
58
|
+
ISSUES=$((ISSUES + 1))
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# Check 4: MCP wrapper
|
|
62
|
+
WRAPPER_PATH="$HOME/.local/bin/bluera-knowledge-mcp"
|
|
63
|
+
echo "Checking MCP wrapper..."
|
|
64
|
+
if [ -f "$WRAPPER_PATH" ]; then
|
|
65
|
+
echo " [OK] MCP wrapper installed at $WRAPPER_PATH"
|
|
66
|
+
else
|
|
67
|
+
echo " [FAIL] MCP wrapper NOT installed"
|
|
68
|
+
echo ""
|
|
69
|
+
echo " FIX: Run setup manually:"
|
|
70
|
+
echo " $PLUGIN_ROOT/scripts/setup.sh"
|
|
71
|
+
echo ""
|
|
72
|
+
ISSUES=$((ISSUES + 1))
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# Check 5: Python 3
|
|
76
|
+
echo "Checking Python 3..."
|
|
77
|
+
if command -v python3 &>/dev/null; then
|
|
78
|
+
PY_VERSION=$(python3 --version 2>&1)
|
|
79
|
+
echo " [OK] $PY_VERSION"
|
|
80
|
+
else
|
|
81
|
+
echo " [WARN] Python 3 not found (optional, needed for embeddings)"
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# Check 6: Playwright
|
|
85
|
+
PLAYWRIGHT_PATH="${PLAYWRIGHT_BROWSERS_PATH:-$HOME/.cache/ms-playwright}"
|
|
86
|
+
echo "Checking Playwright browser..."
|
|
87
|
+
if ls "$PLAYWRIGHT_PATH"/chromium-* 1>/dev/null 2>&1; then
|
|
88
|
+
echo " [OK] Playwright Chromium installed"
|
|
89
|
+
else
|
|
90
|
+
echo " [WARN] Playwright Chromium not found (optional, needed for web crawling)"
|
|
91
|
+
echo " FIX: npx playwright install chromium"
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
echo ""
|
|
95
|
+
echo "═══════════════════════════════════════════════════════════"
|
|
96
|
+
if [ $ISSUES -eq 0 ]; then
|
|
97
|
+
echo " All required checks passed!"
|
|
98
|
+
echo ""
|
|
99
|
+
echo " If MCP is still failing, restart Claude Code."
|
|
100
|
+
else
|
|
101
|
+
echo " Found $ISSUES issue(s) - see FIX instructions above"
|
|
102
|
+
fi
|
|
103
|
+
echo "═══════════════════════════════════════════════════════════"
|