bluera-knowledge 0.11.21 → 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 +15 -0
- package/README.md +22 -0
- package/commands/skill-activation.md +130 -0
- 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/services/watch.service.test.ts +14 -11
package/.claude/council-cache/1a43ed5977b8f29afc79a9bf5c4082ee5ad8338c42ab991a4241a48f80c1e46d.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"provider": "openai",
|
|
3
|
+
"model": "codex-mini-latest",
|
|
4
|
+
"prompt": "What are the best trigger keywords and patterns for a skill auto-activation system in a Claude Code plugin? Context: We're building a UserPromptSubmit hook for the bluera-knowledge plugin that detects when users would benefit from learning about specific skills. The 5 skills are: (1) knowledge-search - how to query indexed library sources, (2) when-to-query - deciding between BK vs Grep/Read, (3) search-optimization - optimizing search parameters, (4) advanced-workflows - multi-tool orchestration, (5) store-lifecycle - managing knowledge stores. Key insight: if a user already uses BK terminology, they don't need the skill suggested. We want to catch users who SHOULD be using BK but don't know it exists.",
|
|
5
|
+
"response": "Below is a bite‑sized “pattern cookbook” you can drop into your `UserPromptSubmit` hook in the bluera‑knowledge Claude Code plugin. The idea is simple:\n\n1. **Look for “natural” signals** that a user is trying to do something Blue‑Knowledge (BK) was built for, but \n2. **Exclude** anyone already using BK lingo (e.g. “knowledge‑search,” “index,” “store,” “workflow”) so you don’t remind the experienced.\n\n---\n\n## 1. Skill → Trigger‑patterns matrix\n\n| Skill | What it is | Trigger patterns (if user text _contains_) | Exclude if user text _contains_ |\n|:---------------------|:---------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------|\n| **knowledge‑search** | How to query an indexed library of docs/sources. | `\\b(find|lookup|search|get me|show me|where can i find)\\b.*\\b(docs|documentation|references?|articles?)\\b` | `\\bknowledge[- ]search\\b|\\bindex(ed)?\\b|\\bstore\\b` |\n| **when‑to‑query** | Deciding whether to use BK vs. grep/`read()` etc. | `\\b(grep|read\\(\\)|scan files|open files manually|search local files)\\b|\\b(is there a better way)\\b` | `\\bwhen[- ]to[- ]query\\b|\\bblue[- ]knowledge\\b` |\n| **search‑optimization** | Tuning filters, relevance, context windows, metadata to get better hits. | `\\b(narrow|filter|refine|limit|boost|prioritize|sort)\\b.*\\b(results|hits|matches)\\b` | `\\bsearch[- ]optimization\\b|\\brelevance\\b|\\bcontext window\\b` |\n| **advanced‑workflows** | Chaining multiple tools/APIs, branching, retrieval‑then‑generation, tool orchestration. | `\\b(chain|pipeline|orchestrate|automate|workflow|multi[- ]step|multi[- ]tool)\\b` | `\\badvanced[- ]workflows\\b|\\btool orchestration\\b` |\n| **store‑lifecycle** | Setting up/updating/archiving knowledge stores, versioning, pruning old info. | `\\b(create|update|delete|archive|prune|version)\\b.*\\b(store|index|database|repository)\\b` | `\\bstore[- ]lifecycle\\b|\\bknowledge[- ]store\\b` |\n\n---\n\n### 1.1. Example Regex snippets\n\n```js\n// snippet for knowledge-search\nconst KNOWLEDGE_SEARCH_RE = /\\b(?:find|lookup|search|get me|show me|where can i find)\\b.*\\b(?:docs|documentation|references?|articles?)\\b/i;\n\n// snippet for when-to-query\nconst WHEN_TO_QUERY_RE = /\\b(?:grep|read\\(\\)|scan files|open files manually|search local files)\\b|\\b(?:is there a better way)\\b/i;\n\n// snippet for store-lifecycle\nconst STORE_LIFECYCLE_RE = /\\b(?:create|update|delete|archive|prune|version)\\b.*\\b(?:store|index|database|repository)\\b/i;\n```\n\n---\n\n## 2. Putting it together in your hook\n\nBelow is a simplified example of how you might wire these patterns into a Claude Code `UserPromptSubmit` hook. You’ll want to adjust the messaging and localization to taste.\n\n```js\n/**\n * bluera-knowledge UserPromptSubmit hook\n * Detect users who could benefit from Blue‑Knowledge skills.\n */\nexport async function onUserPromptSubmit(event, ctx) {\n const text = event.prompt.text;\n\n // quick bailout: if they already speak BK\n const hasBKTerm = /\\bknowledge[- ]search\\b|\\bwhen[- ]to[- ]query\\b|\\bsearch[- ]optimization\\b|\\badvanced[- ]workflows\\b|\\bstore[- ]lifecycle\\b|\\bblue[- ]knowledge\\b/i.test(text);\n if (hasBKTerm) return;\n\n // mapping skill → [pattern, user‐facing hint]\n const SKILLS = [\n {\n name: \"knowledge-search\",\n re: /\\b(?:find|lookup|search|get me|show me|where can i find)\\b.*\\b(?:docs|documentation|references?|articles?)\\b/i,\n hint: \"🔍 You can try using our **knowledge‑search** feature to query indexed docs—no manual grepping needed!\"\n },\n {\n name: \"when-to-query\",\n re: /\\b(?:grep|read\\(\\)|scan files|open files manually|search local files)\\b|\\b(?:is there a better way)\\b/i,\n hint: \"💡 Noticed you're grepping or opening files one by one. Our **when‑to‑query** guide helps decide BK vs. direct file search.\"\n },\n {\n name: \"search-optimization\",\n re: /\\b(?:narrow|filter|refine|limit|boost|prioritize|sort)\\b.*\\b(?:results|hits|matches)\\b/i,\n hint: \"⚙️ Want more relevant hits? See our **search‑optimization** tips for tuning relevance and filters.\"\n },\n {\n name: \"advanced-workflows\",\n re: /\\b(?:chain|pipeline|orchestrate|automate|workflow|multi[- ]step|multi[- ]tool)\\b/i,\n hint: \"🔗 Looking to chain tools or build pipelines? Check out **advanced‑workflows** for multi‑tool orchestration.\"\n },\n {\n name: \"store-lifecycle\",\n re: /\\b(?:create|update|delete|archive|prune|version)\\b.*\\b(?:store|index|database|repository)\\b/i,\n hint: \"🗄️ Managing indexes or archiving old data? Our **store‑lifecycle** guide covers store setup, pruning, and versioning.\"\n }\n ];\n\n // find the first match\n for (const skill of SKILLS) {\n if (skill.re.test(text)) {\n await ctx.reply({\n type: \"skill-suggestion\",\n skill: skill.name,\n message: skill.hint\n });\n break;\n }\n }\n}\n```\n\n---\n\n## 3. Tuning tips\n\n1. **Balance precision/recall**: \n - Start _wide_: shorter patterns catch more but risk noise. \n - Refine as you gather real prompts: update regex to avoid false positives.\n\n2. **Avoid overlap**: \n If two patterns both match, pick the one that feels _most_ immediately helpful. (Above example picks the first.)\n\n3. **User‑facing copy**: \n Keep hints crisp, emoji‑tagged, and link directly to your docs or `@help` commands.\n\n4. **Metrics & feedback loop**: \n Log suggestions shown vs. accepted. Tune patterns around low‑acceptance hits.\n\n---\n\n### Summary\n\nBy combining simple regex‑based detectors with an exclusion of existing BK terminology, you’ll catch “hidden” use cases where users are laboring manually and would love a BK shortcut. Tweak patterns over time against your live traffic, and you’ll have a reliable auto‑activation layer that boosts discoverability _without_ nagging your power users.",
|
|
6
|
+
"timestamp": 1768432278
|
|
7
|
+
}
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
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.1](https://github.com/blueraai/bluera-knowledge/compare/v0.11.21...v0.12.1) (2026-01-14)
|
|
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
|
+
## [0.12.0](https://github.com/blueraai/bluera-knowledge/compare/v0.11.21...v0.12.0) (2026-01-14)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* **hooks:** add skill auto-activation system ([2b4e96b](https://github.com/blueraai/bluera-knowledge/commit/2b4e96bd29f28df63377cdaacab922d4f4321a8f))
|
|
19
|
+
|
|
5
20
|
## [0.11.21](https://github.com/blueraai/bluera-knowledge/compare/v0.11.20...v0.11.21) (2026-01-10)
|
|
6
21
|
|
|
7
22
|
|
package/README.md
CHANGED
|
@@ -1486,6 +1486,28 @@ This hybrid approach reduces unnecessary tool calls and context usage while main
|
|
|
1486
1486
|
|
|
1487
1487
|
Result: Fewer tool calls, more accurate results, less context consumed.
|
|
1488
1488
|
|
|
1489
|
+
### 🎯 Skill Auto-Activation
|
|
1490
|
+
|
|
1491
|
+
Skills can automatically suggest themselves when your prompt matches certain patterns.
|
|
1492
|
+
|
|
1493
|
+
**Toggle via slash command:**
|
|
1494
|
+
- `/bluera-knowledge:skill-activation` - Show current status
|
|
1495
|
+
- `/bluera-knowledge:skill-activation on` - Enable (default)
|
|
1496
|
+
- `/bluera-knowledge:skill-activation off` - Disable
|
|
1497
|
+
- `/bluera-knowledge:skill-activation config` - Toggle individual skills
|
|
1498
|
+
|
|
1499
|
+
**How it works:**
|
|
1500
|
+
When enabled, a UserPromptSubmit hook analyzes your prompt for patterns like:
|
|
1501
|
+
- "How does [library] work?" → suggests `knowledge-search`
|
|
1502
|
+
- "Should I grep or search?" → suggests `when-to-query`
|
|
1503
|
+
- "Too many results" → suggests `search-optimization`
|
|
1504
|
+
- "Multi-step workflow" → suggests `advanced-workflows`
|
|
1505
|
+
- "Add/delete store" → suggests `store-lifecycle`
|
|
1506
|
+
|
|
1507
|
+
Claude evaluates each suggestion and invokes relevant skills before answering. Users who already use BK terminology are excluded (they already know the tool).
|
|
1508
|
+
|
|
1509
|
+
**Configuration stored in:** `~/.local/share/bluera-knowledge/skill-activation.json`
|
|
1510
|
+
|
|
1489
1511
|
---
|
|
1490
1512
|
|
|
1491
1513
|
## 💾 Data Storage
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Toggle skill auto-activation on/off or configure individual skills
|
|
3
|
+
argument-hint: "[on|off|status|config]"
|
|
4
|
+
allowed-tools: ["Read", "Write", "AskUserQuestion"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Skill Activation Configuration
|
|
8
|
+
|
|
9
|
+
Manage the bluera-knowledge skill auto-activation system.
|
|
10
|
+
|
|
11
|
+
## Configuration File
|
|
12
|
+
|
|
13
|
+
Location: `~/.local/share/bluera-knowledge/skill-activation.json`
|
|
14
|
+
|
|
15
|
+
Default configuration (created if missing):
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"enabled": true,
|
|
19
|
+
"threshold": 1,
|
|
20
|
+
"skills": {
|
|
21
|
+
"knowledge-search": true,
|
|
22
|
+
"when-to-query": true,
|
|
23
|
+
"search-optimization": true,
|
|
24
|
+
"advanced-workflows": true,
|
|
25
|
+
"store-lifecycle": true
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Steps
|
|
31
|
+
|
|
32
|
+
### 1. Parse Arguments
|
|
33
|
+
|
|
34
|
+
Extract the subcommand from $ARGUMENTS:
|
|
35
|
+
- Empty or "status": Show current status
|
|
36
|
+
- "on": Enable skill activation
|
|
37
|
+
- "off": Disable skill activation
|
|
38
|
+
- "config": Interactive skill configuration
|
|
39
|
+
|
|
40
|
+
### 2. Read Current Configuration
|
|
41
|
+
|
|
42
|
+
Read `~/.local/share/bluera-knowledge/skill-activation.json`
|
|
43
|
+
|
|
44
|
+
If the file doesn't exist, use the default configuration shown above.
|
|
45
|
+
|
|
46
|
+
### 3. Execute Subcommand
|
|
47
|
+
|
|
48
|
+
**For "status" or empty arguments:**
|
|
49
|
+
|
|
50
|
+
Display the current configuration:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
## Skill Activation Status
|
|
54
|
+
|
|
55
|
+
**Status**: [Enabled/Disabled]
|
|
56
|
+
**Threshold**: [threshold value]
|
|
57
|
+
|
|
58
|
+
### Individual Skills
|
|
59
|
+
| Skill | Status |
|
|
60
|
+
|-------|--------|
|
|
61
|
+
| knowledge-search | enabled/disabled |
|
|
62
|
+
| when-to-query | enabled/disabled |
|
|
63
|
+
| search-optimization | enabled/disabled |
|
|
64
|
+
| advanced-workflows | enabled/disabled |
|
|
65
|
+
| store-lifecycle | enabled/disabled |
|
|
66
|
+
|
|
67
|
+
Use `/bluera-knowledge:skill-activation config` to toggle individual skills.
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**For "on":**
|
|
71
|
+
|
|
72
|
+
1. Read configuration (or use defaults)
|
|
73
|
+
2. Set `enabled: true`
|
|
74
|
+
3. Ensure directory exists: `~/.local/share/bluera-knowledge/`
|
|
75
|
+
4. Write updated configuration
|
|
76
|
+
5. Confirm: "Skill activation **enabled**. Skills will be suggested based on your prompts."
|
|
77
|
+
|
|
78
|
+
**For "off":**
|
|
79
|
+
|
|
80
|
+
1. Read configuration (or use defaults)
|
|
81
|
+
2. Set `enabled: false`
|
|
82
|
+
3. Write updated configuration
|
|
83
|
+
4. Confirm: "Skill activation **disabled**. No skill suggestions will appear."
|
|
84
|
+
|
|
85
|
+
**For "config":**
|
|
86
|
+
|
|
87
|
+
1. Read current configuration
|
|
88
|
+
2. Use AskUserQuestion to let user toggle skills:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"questions": [{
|
|
93
|
+
"question": "Which skills should auto-activate when relevant patterns are detected?",
|
|
94
|
+
"header": "Skills",
|
|
95
|
+
"multiSelect": true,
|
|
96
|
+
"options": [
|
|
97
|
+
{
|
|
98
|
+
"label": "knowledge-search",
|
|
99
|
+
"description": "Suggests when to query BK for library questions"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"label": "when-to-query",
|
|
103
|
+
"description": "Guides BK vs Grep/Read decisions"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"label": "search-optimization",
|
|
107
|
+
"description": "Tips for optimizing search parameters"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"label": "advanced-workflows",
|
|
111
|
+
"description": "Multi-tool orchestration patterns"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"label": "store-lifecycle",
|
|
115
|
+
"description": "Managing knowledge stores"
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}]
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
3. Update skills based on selection (selected = enabled, unselected = disabled)
|
|
123
|
+
4. Write updated configuration
|
|
124
|
+
5. Show updated status table
|
|
125
|
+
|
|
126
|
+
## Notes
|
|
127
|
+
|
|
128
|
+
- The configuration directory is created automatically if it doesn't exist
|
|
129
|
+
- Changes take effect immediately on the next prompt
|
|
130
|
+
- When disabled globally, no skills are suggested regardless of individual settings
|
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
|
@@ -21,6 +21,9 @@ vi.mock('chokidar', () => ({
|
|
|
21
21
|
}),
|
|
22
22
|
}));
|
|
23
23
|
|
|
24
|
+
// Import chokidar once after mock is set up to avoid dynamic import flakiness
|
|
25
|
+
import * as chokidar from 'chokidar';
|
|
26
|
+
|
|
24
27
|
describe('WatchService', () => {
|
|
25
28
|
let watchService: WatchService;
|
|
26
29
|
let mockIndexService: IndexService;
|
|
@@ -78,7 +81,7 @@ describe('WatchService', () => {
|
|
|
78
81
|
const noopErrorHandler = (): void => {};
|
|
79
82
|
|
|
80
83
|
it('starts watching a file store', async () => {
|
|
81
|
-
const { watch } =
|
|
84
|
+
const { watch } = chokidar;
|
|
82
85
|
|
|
83
86
|
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
84
87
|
|
|
@@ -93,7 +96,7 @@ describe('WatchService', () => {
|
|
|
93
96
|
});
|
|
94
97
|
|
|
95
98
|
it('starts watching a repo store', async () => {
|
|
96
|
-
const { watch } =
|
|
99
|
+
const { watch } = chokidar;
|
|
97
100
|
|
|
98
101
|
await watchService.watch(mockRepoStore, 1000, undefined, noopErrorHandler);
|
|
99
102
|
|
|
@@ -109,7 +112,7 @@ describe('WatchService', () => {
|
|
|
109
112
|
});
|
|
110
113
|
|
|
111
114
|
it('does not start watching if already watching the same store', async () => {
|
|
112
|
-
const { watch } =
|
|
115
|
+
const { watch } = chokidar;
|
|
113
116
|
|
|
114
117
|
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
115
118
|
const callCount1 = (watch as ReturnType<typeof vi.fn>).mock.calls.length;
|
|
@@ -121,7 +124,7 @@ describe('WatchService', () => {
|
|
|
121
124
|
});
|
|
122
125
|
|
|
123
126
|
it('allows watching multiple different stores', async () => {
|
|
124
|
-
const { watch } =
|
|
127
|
+
const { watch } = chokidar;
|
|
125
128
|
|
|
126
129
|
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
127
130
|
await watchService.watch(mockRepoStore, 1000, undefined, noopErrorHandler);
|
|
@@ -454,7 +457,7 @@ describe('WatchService', () => {
|
|
|
454
457
|
});
|
|
455
458
|
|
|
456
459
|
it('removes watcher from internal map', async () => {
|
|
457
|
-
const { watch } =
|
|
460
|
+
const { watch } = chokidar;
|
|
458
461
|
|
|
459
462
|
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
460
463
|
await watchService.unwatch(mockFileStore.id);
|
|
@@ -523,7 +526,7 @@ describe('WatchService', () => {
|
|
|
523
526
|
});
|
|
524
527
|
|
|
525
528
|
it('clears all watchers from map', async () => {
|
|
526
|
-
const { watch } =
|
|
529
|
+
const { watch } = chokidar;
|
|
527
530
|
|
|
528
531
|
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
529
532
|
await watchService.watch(mockRepoStore, 1000, undefined, noopErrorHandler);
|
|
@@ -546,7 +549,7 @@ describe('WatchService', () => {
|
|
|
546
549
|
const noopErrorHandler = (): void => {};
|
|
547
550
|
|
|
548
551
|
it('ignores .git directories', async () => {
|
|
549
|
-
const { watch } =
|
|
552
|
+
const { watch } = chokidar;
|
|
550
553
|
|
|
551
554
|
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
552
555
|
|
|
@@ -557,7 +560,7 @@ describe('WatchService', () => {
|
|
|
557
560
|
});
|
|
558
561
|
|
|
559
562
|
it('ignores node_modules directories', async () => {
|
|
560
|
-
const { watch } =
|
|
563
|
+
const { watch } = chokidar;
|
|
561
564
|
|
|
562
565
|
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
563
566
|
|
|
@@ -567,7 +570,7 @@ describe('WatchService', () => {
|
|
|
567
570
|
});
|
|
568
571
|
|
|
569
572
|
it('ignores dist and build directories', async () => {
|
|
570
|
-
const { watch } =
|
|
573
|
+
const { watch } = chokidar;
|
|
571
574
|
|
|
572
575
|
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
573
576
|
|
|
@@ -577,7 +580,7 @@ describe('WatchService', () => {
|
|
|
577
580
|
});
|
|
578
581
|
|
|
579
582
|
it('sets persistent to true', async () => {
|
|
580
|
-
const { watch } =
|
|
583
|
+
const { watch } = chokidar;
|
|
581
584
|
|
|
582
585
|
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
583
586
|
|
|
@@ -586,7 +589,7 @@ describe('WatchService', () => {
|
|
|
586
589
|
});
|
|
587
590
|
|
|
588
591
|
it('sets ignoreInitial to true', async () => {
|
|
589
|
-
const { watch } =
|
|
592
|
+
const { watch } = chokidar;
|
|
590
593
|
|
|
591
594
|
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
592
595
|
|