bluera-knowledge 0.12.2 → 0.12.3
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 +1 -1
- package/CHANGELOG.md +14 -0
- package/hooks/hooks.json +3 -3
- package/hooks/posttooluse-bk-reminder.py +138 -0
- package/hooks/skill-activation.py +20 -16
- package/package.json +1 -1
- package/hooks/pretooluse-bk-reminder.py +0 -101
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
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.12.3](https://github.com/blueraai/bluera-knowledge/compare/v0.11.21...v0.12.3) (2026-01-15)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **hooks:** add PreToolUse hooks for BK suggestions ([23d3fa4](https://github.com/blueraai/bluera-knowledge/commit/23d3fa493dd16427d6bda3ea80064622c6244bba))
|
|
11
|
+
* **hooks:** add skill auto-activation system ([2b4e96b](https://github.com/blueraai/bluera-knowledge/commit/2b4e96bd29f28df63377cdaacab922d4f4321a8f))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* **hooks:** use JSON output for PreToolUse hook context injection ([a73977e](https://github.com/blueraai/bluera-knowledge/commit/a73977e0f8f690d43b9d7c987300522dd501fe38))
|
|
17
|
+
* **tests:** stabilize watch service tests in coverage mode ([fdf6c3a](https://github.com/blueraai/bluera-knowledge/commit/fdf6c3a478adff9e4746b54a9519184ca280f344))
|
|
18
|
+
|
|
5
19
|
## [0.12.2](https://github.com/blueraai/bluera-knowledge/compare/v0.11.21...v0.12.2) (2026-01-15)
|
|
6
20
|
|
|
7
21
|
|
package/hooks/hooks.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"description": "bluera-knowledge plugin hooks - dependency checking, job monitoring, and BK suggestions",
|
|
3
3
|
"hooks": {
|
|
4
|
-
"
|
|
4
|
+
"PostToolUse": [
|
|
5
5
|
{
|
|
6
6
|
"matcher": "Grep",
|
|
7
7
|
"hooks": [
|
|
8
8
|
{
|
|
9
9
|
"type": "command",
|
|
10
|
-
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/
|
|
10
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-bk-reminder.py",
|
|
11
11
|
"timeout": 3
|
|
12
12
|
}
|
|
13
13
|
]
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"hooks": [
|
|
18
18
|
{
|
|
19
19
|
"type": "command",
|
|
20
|
-
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/
|
|
20
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-bk-reminder.py",
|
|
21
21
|
"timeout": 3
|
|
22
22
|
}
|
|
23
23
|
]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
PostToolUse hook for bluera-knowledge plugin.
|
|
4
|
+
Fires after Claude reads/greps in dependency directories,
|
|
5
|
+
reminding to consider using BK for similar future queries.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
# Patterns indicating library/dependency code
|
|
14
|
+
LIBRARY_PATH_PATTERNS = [
|
|
15
|
+
r"node_modules/",
|
|
16
|
+
r"vendor/",
|
|
17
|
+
r"site-packages/",
|
|
18
|
+
r"\.venv/",
|
|
19
|
+
r"venv/",
|
|
20
|
+
r"bower_components/",
|
|
21
|
+
r"packages/.*/node_modules/",
|
|
22
|
+
r"\.npm/",
|
|
23
|
+
r"\.cargo/registry/",
|
|
24
|
+
r"go/pkg/mod/",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
# Compile patterns for efficiency
|
|
28
|
+
LIBRARY_PATTERNS_RE = re.compile("|".join(LIBRARY_PATH_PATTERNS), re.IGNORECASE)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def extract_library_name(path: str) -> str | None:
|
|
32
|
+
"""Extract library name from dependency path."""
|
|
33
|
+
# node_modules/package-name/...
|
|
34
|
+
match = re.search(r"node_modules/(@[^/]+/[^/]+|[^/]+)", path)
|
|
35
|
+
if match:
|
|
36
|
+
return match.group(1)
|
|
37
|
+
|
|
38
|
+
# site-packages/package_name/...
|
|
39
|
+
match = re.search(r"site-packages/([^/]+)", path)
|
|
40
|
+
if match:
|
|
41
|
+
return match.group(1)
|
|
42
|
+
|
|
43
|
+
# vendor/package/...
|
|
44
|
+
match = re.search(r"vendor/([^/]+)", path)
|
|
45
|
+
if match:
|
|
46
|
+
return match.group(1)
|
|
47
|
+
|
|
48
|
+
# .cargo/registry/.../package-name-version/...
|
|
49
|
+
match = re.search(r"\.cargo/registry/[^/]+/([^/]+)-\d", path)
|
|
50
|
+
if match:
|
|
51
|
+
return match.group(1)
|
|
52
|
+
|
|
53
|
+
# go/pkg/mod/package@version/...
|
|
54
|
+
match = re.search(r"go/pkg/mod/([^@]+)@", path)
|
|
55
|
+
if match:
|
|
56
|
+
return match.group(1)
|
|
57
|
+
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def check_grep_tool(tool_input: dict[str, Any]) -> tuple[str | None, str | None]:
|
|
62
|
+
"""Check if Grep targeted library code. Returns (action, library_name)."""
|
|
63
|
+
path = tool_input.get("path", "")
|
|
64
|
+
|
|
65
|
+
if path and LIBRARY_PATTERNS_RE.search(path):
|
|
66
|
+
lib_name = extract_library_name(path)
|
|
67
|
+
return f"grepped in `{path}`", lib_name
|
|
68
|
+
|
|
69
|
+
return None, None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def check_read_tool(tool_input: dict[str, Any]) -> tuple[str | None, str | None]:
|
|
73
|
+
"""Check if Read targeted library code. Returns (action, library_name)."""
|
|
74
|
+
file_path = tool_input.get("file_path", "")
|
|
75
|
+
|
|
76
|
+
if file_path and LIBRARY_PATTERNS_RE.search(file_path):
|
|
77
|
+
lib_name = extract_library_name(file_path)
|
|
78
|
+
return f"read `{file_path}`", lib_name
|
|
79
|
+
|
|
80
|
+
return None, None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def main() -> int:
|
|
84
|
+
try:
|
|
85
|
+
stdin_data = sys.stdin.read()
|
|
86
|
+
if not stdin_data.strip():
|
|
87
|
+
return 0
|
|
88
|
+
hook_input = json.loads(stdin_data)
|
|
89
|
+
except json.JSONDecodeError:
|
|
90
|
+
return 0
|
|
91
|
+
|
|
92
|
+
tool_name = hook_input.get("tool_name", "")
|
|
93
|
+
tool_input = hook_input.get("tool_input", {})
|
|
94
|
+
|
|
95
|
+
action = None
|
|
96
|
+
lib_name = None
|
|
97
|
+
|
|
98
|
+
if tool_name == "Grep":
|
|
99
|
+
action, lib_name = check_grep_tool(tool_input)
|
|
100
|
+
elif tool_name == "Read":
|
|
101
|
+
action, lib_name = check_read_tool(tool_input)
|
|
102
|
+
|
|
103
|
+
if not action:
|
|
104
|
+
return 0
|
|
105
|
+
|
|
106
|
+
# Build context-aware reminder
|
|
107
|
+
lib_hint = f" ({lib_name})" if lib_name else ""
|
|
108
|
+
add_suggestion = (
|
|
109
|
+
f"If {lib_name} is not indexed, consider: /bluera-knowledge:add-repo"
|
|
110
|
+
if lib_name
|
|
111
|
+
else "Consider indexing frequently-used libraries with /bluera-knowledge:add-repo"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
reminder_text = f"""BLUERA-KNOWLEDGE REMINDER
|
|
115
|
+
|
|
116
|
+
You just {action} - this is dependency/library code{lib_hint}.
|
|
117
|
+
|
|
118
|
+
For FUTURE queries about this library, use Bluera Knowledge instead:
|
|
119
|
+
- MCP tool: search(query="your question about {lib_name or 'the library'}")
|
|
120
|
+
- Slash command: /bluera-knowledge:search <query>
|
|
121
|
+
|
|
122
|
+
BK provides indexed, semantic search across library sources - significantly faster
|
|
123
|
+
and more context-efficient than reading through dependency directories.
|
|
124
|
+
|
|
125
|
+
{add_suggestion}"""
|
|
126
|
+
|
|
127
|
+
output = {
|
|
128
|
+
"hookSpecificOutput": {
|
|
129
|
+
"hookEventName": "PostToolUse",
|
|
130
|
+
"additionalContext": reminder_text,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
print(json.dumps(output))
|
|
134
|
+
return 0
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if __name__ == "__main__":
|
|
138
|
+
raise SystemExit(main())
|
|
@@ -95,28 +95,26 @@ def score_skill(
|
|
|
95
95
|
def generate_reminder(
|
|
96
96
|
matches: list[tuple[str, int, list[str], str]]
|
|
97
97
|
) -> str:
|
|
98
|
-
"""Generate
|
|
98
|
+
"""Generate assertive skill activation reminder using forced evaluation pattern."""
|
|
99
99
|
lines = [
|
|
100
|
-
"
|
|
101
|
-
"BLUERA-KNOWLEDGE SKILL ACTIVATION",
|
|
100
|
+
"BLUERA-KNOWLEDGE SKILL ACTIVATION - MANDATORY EVALUATION",
|
|
102
101
|
"",
|
|
103
|
-
"
|
|
104
|
-
"For EACH skill below, decide YES/NO:",
|
|
105
|
-
"- YES: Invoke via Skill tool: Skill(skill='bluera-knowledge:<skill-name>')",
|
|
106
|
-
"- NO: Skip (user doesn't need this guidance)",
|
|
102
|
+
"CRITICAL: Complete these steps IN ORDER before answering:",
|
|
107
103
|
"",
|
|
108
|
-
"
|
|
104
|
+
"STEP 1 - EVALUATE each skill (YES/NO with one-line reason):",
|
|
109
105
|
]
|
|
110
106
|
|
|
111
107
|
for name, score, reasons, description in matches:
|
|
112
|
-
lines.append(f"")
|
|
113
|
-
lines.append(f"
|
|
114
|
-
lines.append(f" Purpose: {description}")
|
|
115
|
-
lines.append(f" Matched: {', '.join(reasons[:3])}")
|
|
108
|
+
lines.append(f" • [{name}] - {description}")
|
|
109
|
+
lines.append(f" Triggered by: {', '.join(reasons[:2])}")
|
|
116
110
|
|
|
117
111
|
lines.append("")
|
|
118
|
-
lines.append("
|
|
119
|
-
lines.append("
|
|
112
|
+
lines.append("STEP 2 - ACTIVATE: For each YES, invoke Skill(skill='bluera-knowledge:<name>') NOW")
|
|
113
|
+
lines.append("")
|
|
114
|
+
lines.append("STEP 3 - RESPOND: Only after activation, answer the user's question")
|
|
115
|
+
lines.append("")
|
|
116
|
+
lines.append("WARNING: This evaluation is WORTHLESS if you skip activation.")
|
|
117
|
+
lines.append("Skills provide indexed library knowledge - faster than grepping node_modules.")
|
|
120
118
|
|
|
121
119
|
return "\n".join(lines)
|
|
122
120
|
|
|
@@ -179,9 +177,15 @@ def main() -> int:
|
|
|
179
177
|
# Sort by score (highest first)
|
|
180
178
|
matches.sort(key=lambda t: t[1], reverse=True)
|
|
181
179
|
|
|
182
|
-
# Generate and output the reminder
|
|
180
|
+
# Generate and output the reminder using proper JSON format
|
|
183
181
|
reminder = generate_reminder(matches)
|
|
184
|
-
|
|
182
|
+
output = {
|
|
183
|
+
"hookSpecificOutput": {
|
|
184
|
+
"hookEventName": "UserPromptSubmit",
|
|
185
|
+
"additionalContext": reminder,
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
print(json.dumps(output))
|
|
185
189
|
|
|
186
190
|
return 0
|
|
187
191
|
|
package/package.json
CHANGED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
PreToolUse hook for bluera-knowledge plugin.
|
|
4
|
-
Fires when Claude is about to Grep/Read in dependency directories,
|
|
5
|
-
reminding to consider using BK instead.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import json
|
|
9
|
-
import re
|
|
10
|
-
import sys
|
|
11
|
-
from typing import Any
|
|
12
|
-
|
|
13
|
-
# Patterns indicating library/dependency code
|
|
14
|
-
LIBRARY_PATH_PATTERNS = [
|
|
15
|
-
r"node_modules/",
|
|
16
|
-
r"vendor/",
|
|
17
|
-
r"site-packages/",
|
|
18
|
-
r"\.venv/",
|
|
19
|
-
r"venv/",
|
|
20
|
-
r"bower_components/",
|
|
21
|
-
r"packages/.*/node_modules/",
|
|
22
|
-
r"\.npm/",
|
|
23
|
-
r"\.cargo/registry/",
|
|
24
|
-
r"go/pkg/mod/",
|
|
25
|
-
]
|
|
26
|
-
|
|
27
|
-
# Compile patterns for efficiency
|
|
28
|
-
LIBRARY_PATTERNS_RE = re.compile("|".join(LIBRARY_PATH_PATTERNS), re.IGNORECASE)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def check_grep_tool(tool_input: dict[str, Any]) -> str | None:
|
|
32
|
-
"""Check if Grep is targeting library code."""
|
|
33
|
-
path = tool_input.get("path", "")
|
|
34
|
-
pattern = tool_input.get("pattern", "")
|
|
35
|
-
|
|
36
|
-
# Check if searching in library directories
|
|
37
|
-
if path and LIBRARY_PATTERNS_RE.search(path):
|
|
38
|
-
return f"grep in `{path}`"
|
|
39
|
-
|
|
40
|
-
# Check if pattern suggests library-specific search
|
|
41
|
-
# (searching for common library internals)
|
|
42
|
-
return None
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def check_read_tool(tool_input: dict[str, Any]) -> str | None:
|
|
46
|
-
"""Check if Read is targeting library code."""
|
|
47
|
-
file_path = tool_input.get("file_path", "")
|
|
48
|
-
|
|
49
|
-
if file_path and LIBRARY_PATTERNS_RE.search(file_path):
|
|
50
|
-
return f"read `{file_path}`"
|
|
51
|
-
|
|
52
|
-
return None
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def main() -> int:
|
|
56
|
-
try:
|
|
57
|
-
stdin_data = sys.stdin.read()
|
|
58
|
-
if not stdin_data.strip():
|
|
59
|
-
return 0
|
|
60
|
-
hook_input = json.loads(stdin_data)
|
|
61
|
-
except json.JSONDecodeError:
|
|
62
|
-
return 0
|
|
63
|
-
|
|
64
|
-
tool_name = hook_input.get("tool_name", "")
|
|
65
|
-
tool_input = hook_input.get("tool_input", {})
|
|
66
|
-
|
|
67
|
-
trigger_reason = None
|
|
68
|
-
|
|
69
|
-
if tool_name == "Grep":
|
|
70
|
-
trigger_reason = check_grep_tool(tool_input)
|
|
71
|
-
elif tool_name == "Read":
|
|
72
|
-
trigger_reason = check_read_tool(tool_input)
|
|
73
|
-
|
|
74
|
-
if not trigger_reason:
|
|
75
|
-
return 0
|
|
76
|
-
|
|
77
|
-
# Output structured JSON for reliable context injection
|
|
78
|
-
reminder_text = f"""BLUERA-KNOWLEDGE SUGGESTION
|
|
79
|
-
|
|
80
|
-
You're about to {trigger_reason} which appears to be dependency/library code.
|
|
81
|
-
|
|
82
|
-
Consider querying Bluera Knowledge instead:
|
|
83
|
-
- Use MCP tool `search` with a relevant query
|
|
84
|
-
- Or invoke `/bluera-knowledge:search <query>`
|
|
85
|
-
|
|
86
|
-
BK provides indexed, searchable access to library sources - faster and more context-efficient than grepping through node_modules.
|
|
87
|
-
|
|
88
|
-
If you don't have this library indexed, continue with your current approach."""
|
|
89
|
-
|
|
90
|
-
output = {
|
|
91
|
-
"hookSpecificOutput": {
|
|
92
|
-
"hookEventName": "PreToolUse",
|
|
93
|
-
"additionalContext": reminder_text,
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
print(json.dumps(output))
|
|
97
|
-
return 0
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if __name__ == "__main__":
|
|
101
|
-
raise SystemExit(main())
|