bluera-knowledge 0.11.20 → 0.12.1
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/council-cache/1a43ed5977b8f29afc79a9bf5c4082ee5ad8338c42ab991a4241a48f80c1e46d.json +7 -0
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +31 -0
- package/README.md +64 -5
- package/commands/crawl.md +7 -7
- package/commands/search.md +9 -2
- package/commands/skill-activation.md +130 -0
- package/dist/{chunk-MQGRQ2EG.js → chunk-C4SYGLAI.js} +27 -7
- package/dist/chunk-C4SYGLAI.js.map +1 -0
- package/dist/{chunk-ZSKQIMD7.js → chunk-CC6EGZ4D.js} +48 -8
- package/dist/chunk-CC6EGZ4D.js.map +1 -0
- package/dist/{chunk-Q2ZGPJ66.js → chunk-QCSFBMYW.js} +2 -2
- package/dist/index.js +64 -12
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +2 -2
- package/dist/workers/background-worker-cli.js +2 -2
- package/hooks/hooks.json +28 -1
- package/hooks/pretooluse-bk-reminder.py +97 -0
- package/hooks/skill-activation.py +190 -0
- package/hooks/skill-rules.json +122 -0
- package/package.json +1 -1
- package/src/analysis/code-graph.test.ts +30 -0
- package/src/analysis/code-graph.ts +10 -2
- package/src/cli/commands/store.test.ts +78 -0
- package/src/cli/commands/store.ts +19 -0
- package/src/cli/commands/sync.test.ts +1 -1
- package/src/cli/commands/sync.ts +50 -1
- package/src/mcp/commands/sync.commands.test.ts +94 -6
- package/src/mcp/commands/sync.commands.ts +36 -6
- package/src/mcp/handlers/search.handler.ts +3 -1
- package/src/mcp/handlers/store.handler.test.ts +3 -0
- package/src/mcp/handlers/store.handler.ts +5 -2
- package/src/mcp/schemas/index.test.ts +36 -0
- package/src/mcp/schemas/index.ts +6 -0
- package/src/mcp/server.ts +11 -0
- package/src/services/code-graph.service.ts +11 -1
- package/src/services/job.service.test.ts +23 -0
- package/src/services/job.service.ts +10 -6
- package/src/services/watch.service.test.ts +14 -11
- package/vitest.config.ts +1 -1
- package/dist/chunk-MQGRQ2EG.js.map +0 -1
- package/dist/chunk-ZSKQIMD7.js.map +0 -1
- /package/dist/{chunk-Q2ZGPJ66.js.map → chunk-QCSFBMYW.js.map} +0 -0
package/dist/mcp/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
IntelligentCrawler
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-QCSFBMYW.js";
|
|
5
5
|
import {
|
|
6
6
|
JobService,
|
|
7
7
|
createDocumentId,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
createServices,
|
|
10
10
|
createStoreId,
|
|
11
11
|
shutdownLogger
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-C4SYGLAI.js";
|
|
13
13
|
import "../chunk-HRQD3MPH.js";
|
|
14
14
|
|
|
15
15
|
// src/workers/background-worker.ts
|
package/hooks/hooks.json
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "bluera-knowledge plugin hooks -
|
|
2
|
+
"description": "bluera-knowledge plugin hooks - dependency checking, job monitoring, and BK suggestions",
|
|
3
3
|
"hooks": {
|
|
4
|
+
"PreToolUse": [
|
|
5
|
+
{
|
|
6
|
+
"matcher": "Grep",
|
|
7
|
+
"hooks": [
|
|
8
|
+
{
|
|
9
|
+
"type": "command",
|
|
10
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse-bk-reminder.py",
|
|
11
|
+
"timeout": 3
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"matcher": "Read",
|
|
17
|
+
"hooks": [
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse-bk-reminder.py",
|
|
21
|
+
"timeout": 3
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
],
|
|
4
26
|
"SessionStart": [
|
|
5
27
|
{
|
|
6
28
|
"hooks": [
|
|
@@ -19,6 +41,11 @@
|
|
|
19
41
|
"type": "command",
|
|
20
42
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/job-status-hook.sh",
|
|
21
43
|
"timeout": 5
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"type": "command",
|
|
47
|
+
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/skill-activation.py",
|
|
48
|
+
"timeout": 5
|
|
22
49
|
}
|
|
23
50
|
]
|
|
24
51
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
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 reminder
|
|
78
|
+
reminder = f"""<system-reminder>
|
|
79
|
+
BLUERA-KNOWLEDGE SUGGESTION
|
|
80
|
+
|
|
81
|
+
You're about to {trigger_reason} which appears to be dependency/library code.
|
|
82
|
+
|
|
83
|
+
Consider querying Bluera Knowledge instead:
|
|
84
|
+
- Use MCP tool `search` with a relevant query
|
|
85
|
+
- Or invoke `/bluera-knowledge:search <query>`
|
|
86
|
+
|
|
87
|
+
BK provides indexed, searchable access to library sources - faster and more context-efficient than grepping through node_modules.
|
|
88
|
+
|
|
89
|
+
If you don't have this library indexed, continue with your current approach.
|
|
90
|
+
</system-reminder>"""
|
|
91
|
+
|
|
92
|
+
print(reminder)
|
|
93
|
+
return 0
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
if __name__ == "__main__":
|
|
97
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Skill activation hook for bluera-knowledge plugin.
|
|
4
|
+
Matches user prompts against skill rules and injects activation reminders.
|
|
5
|
+
|
|
6
|
+
Runs on UserPromptSubmit to detect users who would benefit from learning
|
|
7
|
+
about BK skills, while excluding users who already know BK terminology.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
CONFIG_DIR = Path.home() / ".local" / "share" / "bluera-knowledge"
|
|
18
|
+
CONFIG_FILE = CONFIG_DIR / "skill-activation.json"
|
|
19
|
+
DEFAULT_CONFIG: dict[str, Any] = {
|
|
20
|
+
"enabled": True,
|
|
21
|
+
"threshold": 1,
|
|
22
|
+
"skills": {
|
|
23
|
+
"knowledge-search": True,
|
|
24
|
+
"when-to-query": True,
|
|
25
|
+
"search-optimization": True,
|
|
26
|
+
"advanced-workflows": True,
|
|
27
|
+
"store-lifecycle": True,
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def load_config() -> dict[str, Any]:
|
|
33
|
+
"""Load skill activation configuration."""
|
|
34
|
+
if not CONFIG_FILE.exists():
|
|
35
|
+
return DEFAULT_CONFIG.copy()
|
|
36
|
+
try:
|
|
37
|
+
with open(CONFIG_FILE, encoding="utf-8") as f:
|
|
38
|
+
return json.load(f)
|
|
39
|
+
except (json.JSONDecodeError, IOError):
|
|
40
|
+
return DEFAULT_CONFIG.copy()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def load_rules(plugin_root: Path) -> dict[str, Any]:
|
|
44
|
+
"""Load skill rules from plugin hooks directory."""
|
|
45
|
+
rules_path = plugin_root / "hooks" / "skill-rules.json"
|
|
46
|
+
if not rules_path.exists():
|
|
47
|
+
return {"skills": [], "threshold": 1, "globalExclusions": []}
|
|
48
|
+
with open(rules_path, encoding="utf-8") as f:
|
|
49
|
+
return json.load(f)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def matches_condition(prompt: str, condition: dict[str, Any]) -> bool:
|
|
53
|
+
"""Check if prompt matches a single condition (keyword or regex)."""
|
|
54
|
+
prompt_lower = prompt.lower()
|
|
55
|
+
if "keyword" in condition:
|
|
56
|
+
return condition["keyword"].lower() in prompt_lower
|
|
57
|
+
if "regex" in condition:
|
|
58
|
+
return bool(re.search(condition["regex"], prompt, flags=re.IGNORECASE))
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def check_exclusions(
|
|
63
|
+
prompt: str, exclusions: list[dict[str, Any]]
|
|
64
|
+
) -> bool:
|
|
65
|
+
"""Check if any exclusion pattern matches. Returns True if excluded."""
|
|
66
|
+
for exc in exclusions:
|
|
67
|
+
if matches_condition(prompt, exc):
|
|
68
|
+
return True
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def score_skill(
|
|
73
|
+
prompt: str, skill: dict[str, Any]
|
|
74
|
+
) -> tuple[int, list[str]]:
|
|
75
|
+
"""Score a skill against the user prompt. Returns (score, reasons)."""
|
|
76
|
+
reasons: list[str] = []
|
|
77
|
+
score = 0
|
|
78
|
+
|
|
79
|
+
# Check skill-specific exclusions first
|
|
80
|
+
if check_exclusions(prompt, skill.get("exclusions", [])):
|
|
81
|
+
return 0, []
|
|
82
|
+
|
|
83
|
+
for trigger in skill.get("triggers", []):
|
|
84
|
+
if matches_condition(prompt, trigger):
|
|
85
|
+
weight = trigger.get("weight", 1)
|
|
86
|
+
score += weight
|
|
87
|
+
if "keyword" in trigger:
|
|
88
|
+
reasons.append(f'keyword "{trigger["keyword"]}"')
|
|
89
|
+
elif "regex" in trigger:
|
|
90
|
+
reasons.append(f'pattern match')
|
|
91
|
+
|
|
92
|
+
return score, reasons
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def generate_reminder(
|
|
96
|
+
matches: list[tuple[str, int, list[str], str]]
|
|
97
|
+
) -> str:
|
|
98
|
+
"""Generate the system-reminder for matched skills."""
|
|
99
|
+
lines = [
|
|
100
|
+
"<system-reminder>",
|
|
101
|
+
"BLUERA-KNOWLEDGE SKILL ACTIVATION",
|
|
102
|
+
"",
|
|
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)",
|
|
107
|
+
"",
|
|
108
|
+
"Candidate skills (ranked by relevance):",
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
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])}")
|
|
116
|
+
|
|
117
|
+
lines.append("")
|
|
118
|
+
lines.append("Evaluate quickly, then answer the user's question.")
|
|
119
|
+
lines.append("</system-reminder>")
|
|
120
|
+
|
|
121
|
+
return "\n".join(lines)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def main() -> int:
|
|
125
|
+
# Load configuration
|
|
126
|
+
config = load_config()
|
|
127
|
+
|
|
128
|
+
# Check if skill activation is enabled
|
|
129
|
+
if not config.get("enabled", True):
|
|
130
|
+
return 0
|
|
131
|
+
|
|
132
|
+
# Get plugin root from environment
|
|
133
|
+
plugin_root_env = os.environ.get("CLAUDE_PLUGIN_ROOT", "")
|
|
134
|
+
if not plugin_root_env:
|
|
135
|
+
return 0
|
|
136
|
+
plugin_root = Path(plugin_root_env)
|
|
137
|
+
|
|
138
|
+
# Read hook input from stdin
|
|
139
|
+
try:
|
|
140
|
+
stdin_data = sys.stdin.read()
|
|
141
|
+
if not stdin_data.strip():
|
|
142
|
+
return 0
|
|
143
|
+
hook_input = json.loads(stdin_data)
|
|
144
|
+
except json.JSONDecodeError:
|
|
145
|
+
return 0
|
|
146
|
+
|
|
147
|
+
prompt = hook_input.get("prompt", "")
|
|
148
|
+
if not prompt.strip():
|
|
149
|
+
return 0
|
|
150
|
+
|
|
151
|
+
# Load rules
|
|
152
|
+
rules = load_rules(plugin_root)
|
|
153
|
+
|
|
154
|
+
# Check global exclusions first
|
|
155
|
+
if check_exclusions(prompt, rules.get("globalExclusions", [])):
|
|
156
|
+
return 0
|
|
157
|
+
|
|
158
|
+
threshold = config.get("threshold", rules.get("threshold", 1))
|
|
159
|
+
enabled_skills = config.get("skills", {})
|
|
160
|
+
|
|
161
|
+
# Score each skill
|
|
162
|
+
matches: list[tuple[str, int, list[str], str]] = []
|
|
163
|
+
|
|
164
|
+
for skill in rules.get("skills", []):
|
|
165
|
+
name = skill["name"]
|
|
166
|
+
|
|
167
|
+
# Skip disabled skills
|
|
168
|
+
if not enabled_skills.get(name, True):
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
score, reasons = score_skill(prompt, skill)
|
|
172
|
+
if score >= threshold:
|
|
173
|
+
matches.append((name, score, reasons, skill.get("description", "")))
|
|
174
|
+
|
|
175
|
+
# No matches - silent exit
|
|
176
|
+
if not matches:
|
|
177
|
+
return 0
|
|
178
|
+
|
|
179
|
+
# Sort by score (highest first)
|
|
180
|
+
matches.sort(key=lambda t: t[1], reverse=True)
|
|
181
|
+
|
|
182
|
+
# Generate and output the reminder
|
|
183
|
+
reminder = generate_reminder(matches)
|
|
184
|
+
print(reminder)
|
|
185
|
+
|
|
186
|
+
return 0
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
if __name__ == "__main__":
|
|
190
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "bluera-knowledge skill activation rules - technology-agnostic patterns for development scenarios",
|
|
3
|
+
"version": 2,
|
|
4
|
+
"globalExclusions": [
|
|
5
|
+
{ "keyword": "bluera-knowledge" },
|
|
6
|
+
{ "keyword": "bluera knowledge" },
|
|
7
|
+
{ "keyword": "/bluera-knowledge:" },
|
|
8
|
+
{ "regex": "mcp__.*bluera" }
|
|
9
|
+
],
|
|
10
|
+
"threshold": 2,
|
|
11
|
+
"skills": [
|
|
12
|
+
{
|
|
13
|
+
"name": "knowledge-search",
|
|
14
|
+
"description": "How to query Bluera Knowledge for library/dependency questions",
|
|
15
|
+
"triggers": [
|
|
16
|
+
{ "regex": "the\\s+\\w+(-\\w+)*\\s+(package|library|module|framework|dependency)", "weight": 3 },
|
|
17
|
+
{ "regex": "\\w+(-\\w+)*\\s+(package|library|module)\\s+(is|does|keeps|isn't|won't|doesn't)", "weight": 3 },
|
|
18
|
+
{ "regex": "\\w+(-\\w+)*\\s+(documentation|docs)\\b", "weight": 2 },
|
|
19
|
+
{ "regex": "error\\s+(from|in|with)\\s+(the\\s+)?\\w+(-\\w+)*\\s+(package|library|module)", "weight": 3 },
|
|
20
|
+
{ "regex": "(package|library|dependency|module)\\s+(is\\s+)?(throwing|throws|error|failing)", "weight": 3 },
|
|
21
|
+
{ "regex": "how\\s+does\\s+(the\\s+)?\\w+(-\\w+)*\\s+(package|library|module|framework)\\s+(handle|work|process)", "weight": 3 },
|
|
22
|
+
{ "regex": "what\\s+does\\s+(the\\s+)?\\w+(-\\w+)*\\s+(package|library)\\s+(do|return|accept)", "weight": 3 },
|
|
23
|
+
{ "regex": "why\\s+does\\s+(the\\s+)?\\w+(-\\w+)*\\s+(package|library|module)", "weight": 3 },
|
|
24
|
+
{ "regex": "(configure|config|settings)\\s+(for\\s+)?(the\\s+)?\\w+(-\\w+)*\\s+(package|library)", "weight": 3 },
|
|
25
|
+
{ "regex": "(upgraded?|updated?)\\s+(the\\s+)?\\w+(-\\w+)*\\s+(package|library|dependency)", "weight": 2 },
|
|
26
|
+
{ "regex": "integrate\\s+(the\\s+)?\\w+(-\\w+)*\\s+(package|library)", "weight": 2 },
|
|
27
|
+
{ "regex": "dependency\\s+(is|keeps|isn't|won't|error|issue|problem)", "weight": 2 },
|
|
28
|
+
{ "regex": "third[- ]party\\s+(library|package|code)", "weight": 2 }
|
|
29
|
+
],
|
|
30
|
+
"exclusions": [
|
|
31
|
+
{ "keyword": "my code" },
|
|
32
|
+
{ "keyword": "our code" },
|
|
33
|
+
{ "keyword": "this function" },
|
|
34
|
+
{ "keyword": "this file" },
|
|
35
|
+
{ "keyword": "this component" },
|
|
36
|
+
{ "keyword": "this class" },
|
|
37
|
+
{ "keyword": "this project" },
|
|
38
|
+
{ "keyword": "I wrote" },
|
|
39
|
+
{ "keyword": "we wrote" },
|
|
40
|
+
{ "keyword": "my implementation" },
|
|
41
|
+
{ "keyword": "store" },
|
|
42
|
+
{ "keyword": "index" }
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "when-to-query",
|
|
47
|
+
"description": "Decision guide for Bluera Knowledge vs Grep/Read",
|
|
48
|
+
"triggers": [
|
|
49
|
+
{ "keyword": "should i grep", "weight": 2 },
|
|
50
|
+
{ "keyword": "where should i look", "weight": 2 },
|
|
51
|
+
{ "keyword": "grep or search", "weight": 2 },
|
|
52
|
+
{ "keyword": "search or grep", "weight": 2 },
|
|
53
|
+
{ "regex": "where\\s+(should|do)\\s+i\\s+(find|look)\\s+.*(library|package|dependency)", "weight": 3 },
|
|
54
|
+
{ "regex": "is\\s+there\\s+a\\s+better\\s+way\\s+to\\s+(search|find)", "weight": 2 },
|
|
55
|
+
{ "regex": "should\\s+i\\s+(use\\s+)?grep\\s+(for|to)", "weight": 2 },
|
|
56
|
+
{ "regex": "how\\s+(do|should)\\s+i\\s+find.*(in|from)\\s+(a\\s+)?(library|package|dependency)", "weight": 3 }
|
|
57
|
+
],
|
|
58
|
+
"exclusions": [
|
|
59
|
+
{ "keyword": "store" },
|
|
60
|
+
{ "keyword": "index" }
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "search-optimization",
|
|
65
|
+
"description": "Optimizing search parameters and token usage",
|
|
66
|
+
"triggers": [
|
|
67
|
+
{ "keyword": "too many results", "weight": 2 },
|
|
68
|
+
{ "keyword": "too few results", "weight": 2 },
|
|
69
|
+
{ "keyword": "limit results", "weight": 2 },
|
|
70
|
+
{ "keyword": "reduce tokens", "weight": 2 },
|
|
71
|
+
{ "keyword": "token usage", "weight": 2 },
|
|
72
|
+
{ "keyword": "optimize search", "weight": 2 },
|
|
73
|
+
{ "keyword": "detail level", "weight": 2 },
|
|
74
|
+
{ "regex": "\\b(minimal|contextual|full)\\s+detail", "weight": 2 },
|
|
75
|
+
{ "regex": "\\b(vector|fts|hybrid)\\s+(search|mode)", "weight": 2 },
|
|
76
|
+
{ "regex": "narrow\\s+(down\\s+)?(the\\s+)?results", "weight": 2 },
|
|
77
|
+
{ "regex": "search\\s+(is\\s+)?(returning|giving)\\s+(too\\s+)?(many|few)", "weight": 2 }
|
|
78
|
+
],
|
|
79
|
+
"exclusions": [
|
|
80
|
+
{ "regex": "--?(limit|detail|mode|threshold)\\s*=" }
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"name": "advanced-workflows",
|
|
85
|
+
"description": "Multi-tool orchestration patterns",
|
|
86
|
+
"triggers": [
|
|
87
|
+
{ "keyword": "multi-step", "weight": 2 },
|
|
88
|
+
{ "keyword": "orchestration", "weight": 2 },
|
|
89
|
+
{ "keyword": "job monitoring", "weight": 2 },
|
|
90
|
+
{ "keyword": "background job", "weight": 2 },
|
|
91
|
+
{ "keyword": "combine tools", "weight": 2 },
|
|
92
|
+
{ "keyword": "chain operations", "weight": 2 },
|
|
93
|
+
{ "regex": "chain.*searches", "weight": 2 },
|
|
94
|
+
{ "regex": "multiple.*searches", "weight": 2 },
|
|
95
|
+
{ "regex": "search.*then\\s+(summarize|extract|filter)", "weight": 2 },
|
|
96
|
+
{ "regex": "for\\s+each\\s+(search\\s+)?(result|match)", "weight": 2 }
|
|
97
|
+
],
|
|
98
|
+
"exclusions": []
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"name": "store-lifecycle",
|
|
102
|
+
"description": "Managing knowledge stores",
|
|
103
|
+
"triggers": [
|
|
104
|
+
{ "keyword": "add store", "weight": 2 },
|
|
105
|
+
{ "keyword": "create store", "weight": 2 },
|
|
106
|
+
{ "keyword": "delete store", "weight": 2 },
|
|
107
|
+
{ "keyword": "remove store", "weight": 2 },
|
|
108
|
+
{ "keyword": "index store", "weight": 2 },
|
|
109
|
+
{ "keyword": "re-index", "weight": 2 },
|
|
110
|
+
{ "keyword": "reindex", "weight": 2 },
|
|
111
|
+
{ "keyword": "knowledge store", "weight": 2 },
|
|
112
|
+
{ "regex": "add\\s+(a\\s+)?(repo|repository|folder|directory)\\s+(to|for)\\s+(knowledge|indexing|search)", "weight": 3 },
|
|
113
|
+
{ "regex": "index\\s+(a|the|my)\\s+(repo|repository|folder|directory|library|package)", "weight": 2 },
|
|
114
|
+
{ "regex": "set\\s+up.*(knowledge|search)\\s*(store|index)", "weight": 2 },
|
|
115
|
+
{ "regex": "(backup|snapshot|archive).*(knowledge|search)\\s*(store|index)", "weight": 2 }
|
|
116
|
+
],
|
|
117
|
+
"exclusions": [
|
|
118
|
+
{ "regex": "/bluera-knowledge:(add-repo|add-folder|remove-store|index)" }
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
}
|
package/package.json
CHANGED
|
@@ -328,6 +328,36 @@ describe('CodeGraph', () => {
|
|
|
328
328
|
expect(edgeTypes).toContain('calls');
|
|
329
329
|
});
|
|
330
330
|
|
|
331
|
+
it('includes confidence in serialized edges', () => {
|
|
332
|
+
const graph = new CodeGraph();
|
|
333
|
+
const nodes: CodeNode[] = [
|
|
334
|
+
{
|
|
335
|
+
type: 'function',
|
|
336
|
+
name: 'fn',
|
|
337
|
+
exported: false,
|
|
338
|
+
startLine: 1,
|
|
339
|
+
endLine: 2,
|
|
340
|
+
},
|
|
341
|
+
];
|
|
342
|
+
|
|
343
|
+
graph.addNodes(nodes, '/src/test.ts');
|
|
344
|
+
graph.addImport('/src/test.ts', 'module', ['util']); // confidence: 1.0
|
|
345
|
+
graph.analyzeCallRelationships('other();', '/src/test.ts', 'fn'); // confidence: 0.5
|
|
346
|
+
|
|
347
|
+
const json = graph.toJSON();
|
|
348
|
+
|
|
349
|
+
// All edges should have confidence property preserved
|
|
350
|
+
expect(json.edges.every((e) => typeof e.confidence === 'number')).toBe(true);
|
|
351
|
+
|
|
352
|
+
// Import edges have confidence 1.0
|
|
353
|
+
const importEdge = json.edges.find((e) => e.type === 'imports');
|
|
354
|
+
expect(importEdge?.confidence).toBe(1.0);
|
|
355
|
+
|
|
356
|
+
// Call edges from regex detection have confidence 0.5
|
|
357
|
+
const callEdge = json.edges.find((e) => e.type === 'calls');
|
|
358
|
+
expect(callEdge?.confidence).toBe(0.5);
|
|
359
|
+
});
|
|
360
|
+
|
|
331
361
|
it('handles empty graph', () => {
|
|
332
362
|
const graph = new CodeGraph();
|
|
333
363
|
const json = graph.toJSON();
|
|
@@ -227,7 +227,10 @@ export class CodeGraph {
|
|
|
227
227
|
return importPath;
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
toJSON(): {
|
|
230
|
+
toJSON(): {
|
|
231
|
+
nodes: GraphNode[];
|
|
232
|
+
edges: Array<{ from: string; to: string; type: string; confidence: number }>;
|
|
233
|
+
} {
|
|
231
234
|
const allEdges: GraphEdge[] = [];
|
|
232
235
|
for (const edges of this.edges.values()) {
|
|
233
236
|
allEdges.push(...edges);
|
|
@@ -235,7 +238,12 @@ export class CodeGraph {
|
|
|
235
238
|
|
|
236
239
|
return {
|
|
237
240
|
nodes: Array.from(this.nodes.values()),
|
|
238
|
-
edges: allEdges.map((e) => ({
|
|
241
|
+
edges: allEdges.map((e) => ({
|
|
242
|
+
from: e.from,
|
|
243
|
+
to: e.to,
|
|
244
|
+
type: e.type,
|
|
245
|
+
confidence: e.confidence,
|
|
246
|
+
})),
|
|
239
247
|
};
|
|
240
248
|
}
|
|
241
249
|
}
|
|
@@ -17,8 +17,23 @@ interface MockStoreService {
|
|
|
17
17
|
delete: MockInstance;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
interface MockLanceService {
|
|
21
|
+
deleteStore: MockInstance;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface MockCodeGraphService {
|
|
25
|
+
deleteGraph: MockInstance;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface MockConfigService {
|
|
29
|
+
resolveDataDir: MockInstance;
|
|
30
|
+
}
|
|
31
|
+
|
|
20
32
|
interface MockServices {
|
|
21
33
|
store: MockStoreService;
|
|
34
|
+
lance: MockLanceService;
|
|
35
|
+
codeGraph: MockCodeGraphService;
|
|
36
|
+
config: MockConfigService;
|
|
22
37
|
}
|
|
23
38
|
|
|
24
39
|
describe('store command execution', () => {
|
|
@@ -38,6 +53,15 @@ describe('store command execution', () => {
|
|
|
38
53
|
create: vi.fn(),
|
|
39
54
|
delete: vi.fn(),
|
|
40
55
|
},
|
|
56
|
+
lance: {
|
|
57
|
+
deleteStore: vi.fn().mockResolvedValue(undefined),
|
|
58
|
+
},
|
|
59
|
+
codeGraph: {
|
|
60
|
+
deleteGraph: vi.fn().mockResolvedValue(undefined),
|
|
61
|
+
},
|
|
62
|
+
config: {
|
|
63
|
+
resolveDataDir: vi.fn().mockReturnValue('/tmp/test-data'),
|
|
64
|
+
},
|
|
41
65
|
};
|
|
42
66
|
|
|
43
67
|
vi.mocked(createServices).mockResolvedValue(mockServices);
|
|
@@ -366,6 +390,51 @@ describe('store command execution', () => {
|
|
|
366
390
|
});
|
|
367
391
|
});
|
|
368
392
|
|
|
393
|
+
it('creates repo store with branch option', async () => {
|
|
394
|
+
const mockStore: RepoStore = {
|
|
395
|
+
id: createStoreId('new-store-6'),
|
|
396
|
+
name: 'branched-repo',
|
|
397
|
+
type: 'repo',
|
|
398
|
+
path: '/path/to/cloned/repo',
|
|
399
|
+
url: 'https://github.com/user/repo',
|
|
400
|
+
branch: 'develop',
|
|
401
|
+
createdAt: new Date(),
|
|
402
|
+
updatedAt: new Date(),
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
mockServices.store.create.mockResolvedValue({
|
|
406
|
+
success: true,
|
|
407
|
+
data: mockStore,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const command = createStoreCommand(getOptions);
|
|
411
|
+
const createCommand = command.commands.find((c) => c.name() === 'create');
|
|
412
|
+
const actionHandler = createCommand?._actionHandler;
|
|
413
|
+
|
|
414
|
+
createCommand.parseOptions([
|
|
415
|
+
'--type',
|
|
416
|
+
'repo',
|
|
417
|
+
'--source',
|
|
418
|
+
'https://github.com/user/repo',
|
|
419
|
+
'--branch',
|
|
420
|
+
'develop',
|
|
421
|
+
]);
|
|
422
|
+
await actionHandler!(['branched-repo']);
|
|
423
|
+
|
|
424
|
+
expect(mockServices.store.create).toHaveBeenCalledWith({
|
|
425
|
+
name: 'branched-repo',
|
|
426
|
+
type: 'repo',
|
|
427
|
+
path: undefined,
|
|
428
|
+
url: 'https://github.com/user/repo',
|
|
429
|
+
branch: 'develop',
|
|
430
|
+
description: undefined,
|
|
431
|
+
tags: undefined,
|
|
432
|
+
});
|
|
433
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
434
|
+
expect.stringContaining('Created store: branched-repo')
|
|
435
|
+
);
|
|
436
|
+
});
|
|
437
|
+
|
|
369
438
|
it('outputs JSON when format is json', async () => {
|
|
370
439
|
const mockStore: FileStore = {
|
|
371
440
|
id: createStoreId('new-store-5'),
|
|
@@ -860,6 +929,15 @@ describe('store command execution', () => {
|
|
|
860
929
|
expect(tagsOption?.mandatory).toBe(false);
|
|
861
930
|
});
|
|
862
931
|
|
|
932
|
+
it('create subcommand has --branch option for repo type', () => {
|
|
933
|
+
const command = createStoreCommand(getOptions);
|
|
934
|
+
const createCommand = command.commands.find((c) => c.name() === 'create');
|
|
935
|
+
const branchOption = createCommand?.options.find((o) => o.long === '--branch');
|
|
936
|
+
|
|
937
|
+
expect(branchOption).toBeDefined();
|
|
938
|
+
expect(branchOption?.mandatory).toBe(false);
|
|
939
|
+
});
|
|
940
|
+
|
|
863
941
|
it('delete subcommand has force and yes options', () => {
|
|
864
942
|
const command = createStoreCommand(getOptions);
|
|
865
943
|
const deleteCommand = command.commands.find((c) => c.name() === 'delete');
|