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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bluera-knowledge",
3
- "version": "0.12.2",
3
+ "version": "0.12.3",
4
4
  "description": "Clone repos, crawl docs, search locally. Fast, authoritative answers for AI coding agents.",
5
5
  "mcpServers": {
6
6
  "bluera-knowledge": {
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
- "PreToolUse": [
4
+ "PostToolUse": [
5
5
  {
6
6
  "matcher": "Grep",
7
7
  "hooks": [
8
8
  {
9
9
  "type": "command",
10
- "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse-bk-reminder.py",
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/pretooluse-bk-reminder.py",
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 the system-reminder for matched skills."""
98
+ """Generate assertive skill activation reminder using forced evaluation pattern."""
99
99
  lines = [
100
- "<system-reminder>",
101
- "BLUERA-KNOWLEDGE SKILL ACTIVATION",
100
+ "BLUERA-KNOWLEDGE SKILL ACTIVATION - MANDATORY EVALUATION",
102
101
  "",
103
- "The user's prompt suggests they may benefit from these skills.",
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
- "Candidate skills (ranked by relevance):",
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" [{name}] (score={score})")
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("Evaluate quickly, then answer the user's question.")
119
- lines.append("</system-reminder>")
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
- print(reminder)
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,6 +1,6 @@
1
1
  {
2
2
  "name": "bluera-knowledge",
3
- "version": "0.12.2",
3
+ "version": "0.12.3",
4
4
  "description": "CLI tool for managing knowledge stores with semantic search",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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())