ruvnet-kb-first 5.0.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/LICENSE +21 -0
- package/README.md +674 -0
- package/SKILL.md +740 -0
- package/bin/kb-first.js +123 -0
- package/install/init-project.sh +435 -0
- package/install/install-global.sh +257 -0
- package/install/kb-first-autodetect.sh +108 -0
- package/install/kb-first-command.md +80 -0
- package/install/kb-first-skill.md +262 -0
- package/package.json +87 -0
- package/phases/00-assessment.md +529 -0
- package/phases/01-storage.md +194 -0
- package/phases/01.5-hooks-setup.md +521 -0
- package/phases/02-kb-creation.md +413 -0
- package/phases/03-persistence.md +125 -0
- package/phases/04-visualization.md +170 -0
- package/phases/05-integration.md +114 -0
- package/phases/06-scaffold.md +130 -0
- package/phases/07-build.md +493 -0
- package/phases/08-verification.md +597 -0
- package/phases/09-security.md +512 -0
- package/phases/10-documentation.md +613 -0
- package/phases/11-deployment.md +670 -0
- package/phases/testing.md +713 -0
- package/scripts/1.5-hooks-verify.sh +252 -0
- package/scripts/8.1-code-scan.sh +58 -0
- package/scripts/8.2-import-check.sh +42 -0
- package/scripts/8.3-source-returns.sh +52 -0
- package/scripts/8.4-startup-verify.sh +65 -0
- package/scripts/8.5-fallback-check.sh +63 -0
- package/scripts/8.6-attribution.sh +56 -0
- package/scripts/8.7-confidence.sh +56 -0
- package/scripts/8.8-gap-logging.sh +70 -0
- package/scripts/9-security-audit.sh +202 -0
- package/scripts/init-project.sh +395 -0
- package/scripts/verify-enforcement.sh +167 -0
- package/src/commands/hooks.js +361 -0
- package/src/commands/init.js +315 -0
- package/src/commands/phase.js +372 -0
- package/src/commands/score.js +380 -0
- package/src/commands/status.js +193 -0
- package/src/commands/verify.js +286 -0
- package/src/index.js +56 -0
- package/src/mcp-server.js +412 -0
- package/templates/attention-router.ts +534 -0
- package/templates/code-analysis.ts +683 -0
- package/templates/federated-kb-learner.ts +649 -0
- package/templates/gnn-engine.ts +1091 -0
- package/templates/intentions.md +277 -0
- package/templates/kb-client.ts +905 -0
- package/templates/schema.sql +303 -0
- package/templates/sona-config.ts +312 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# KB-First Final Verification Script
|
|
3
|
+
# Run this to verify all KB enforcement rules are followed
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
echo "=== KB-First v3.0 Verification ==="
|
|
8
|
+
echo ""
|
|
9
|
+
|
|
10
|
+
errors=0
|
|
11
|
+
warnings=0
|
|
12
|
+
|
|
13
|
+
# Color codes
|
|
14
|
+
RED='\033[0;31m'
|
|
15
|
+
GREEN='\033[0;32m'
|
|
16
|
+
YELLOW='\033[1;33m'
|
|
17
|
+
NC='\033[0m' # No Color
|
|
18
|
+
|
|
19
|
+
pass() {
|
|
20
|
+
echo -e " ${GREEN}✅${NC} $1"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fail() {
|
|
24
|
+
echo -e " ${RED}❌${NC} $1"
|
|
25
|
+
errors=$((errors + 1))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
warn() {
|
|
29
|
+
echo -e " ${YELLOW}⚠️${NC} $1"
|
|
30
|
+
warnings=$((warnings + 1))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# ============================================
|
|
34
|
+
# Rule 1: No Hardcoded Domain Logic
|
|
35
|
+
# ============================================
|
|
36
|
+
echo "Rule 1: No hardcoded domain logic..."
|
|
37
|
+
|
|
38
|
+
if [ -d "src/domain" ]; then
|
|
39
|
+
# Check for hardcoded decimals (likely rates/percentages)
|
|
40
|
+
hardcoded=$(grep -rnE "= 0\.[0-9]+" src/domain/ 2>/dev/null | grep -v "confidence" | grep -v "test" || true)
|
|
41
|
+
if [ -n "$hardcoded" ]; then
|
|
42
|
+
fail "Found potential hardcoded decimals:"
|
|
43
|
+
echo "$hardcoded" | head -5
|
|
44
|
+
else
|
|
45
|
+
pass "No hardcoded decimals found"
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Check for DEFAULT_ constants
|
|
49
|
+
defaults=$(grep -rn "DEFAULT_" src/domain/ 2>/dev/null || true)
|
|
50
|
+
if [ -n "$defaults" ]; then
|
|
51
|
+
fail "Found DEFAULT_ constants:"
|
|
52
|
+
echo "$defaults" | head -5
|
|
53
|
+
else
|
|
54
|
+
pass "No DEFAULT_ constants found"
|
|
55
|
+
fi
|
|
56
|
+
else
|
|
57
|
+
warn "src/domain/ directory not found"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# ============================================
|
|
61
|
+
# Rule 2: All Domain Files Import KB
|
|
62
|
+
# ============================================
|
|
63
|
+
echo ""
|
|
64
|
+
echo "Rule 2: All domain files import from kb/..."
|
|
65
|
+
|
|
66
|
+
if [ -d "src/domain" ]; then
|
|
67
|
+
for file in src/domain/*.ts; do
|
|
68
|
+
if [ -f "$file" ]; then
|
|
69
|
+
if ! grep -q "from.*kb" "$file"; then
|
|
70
|
+
fail "$file does not import from kb/"
|
|
71
|
+
fi
|
|
72
|
+
fi
|
|
73
|
+
done
|
|
74
|
+
|
|
75
|
+
if [ $errors -eq 0 ]; then
|
|
76
|
+
pass "All domain files import from kb/"
|
|
77
|
+
fi
|
|
78
|
+
else
|
|
79
|
+
warn "src/domain/ directory not found"
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# ============================================
|
|
83
|
+
# Rule 3: Responses Include kbSources
|
|
84
|
+
# ============================================
|
|
85
|
+
echo ""
|
|
86
|
+
echo "Rule 3: Responses include kbSources..."
|
|
87
|
+
|
|
88
|
+
if [ -d "src/domain" ]; then
|
|
89
|
+
missing_sources=$(grep -rL "kbSources" src/domain/*.ts 2>/dev/null || true)
|
|
90
|
+
if [ -n "$missing_sources" ]; then
|
|
91
|
+
warn "Files may not return kbSources:"
|
|
92
|
+
echo "$missing_sources"
|
|
93
|
+
else
|
|
94
|
+
pass "kbSources found in domain files"
|
|
95
|
+
fi
|
|
96
|
+
else
|
|
97
|
+
warn "src/domain/ directory not found"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# ============================================
|
|
101
|
+
# Rule 4: Startup Verification
|
|
102
|
+
# ============================================
|
|
103
|
+
echo ""
|
|
104
|
+
echo "Rule 4: Startup verification..."
|
|
105
|
+
|
|
106
|
+
if [ -f "src/index.ts" ]; then
|
|
107
|
+
if grep -q "verifyConnection" src/index.ts; then
|
|
108
|
+
pass "Entry point calls verifyConnection"
|
|
109
|
+
else
|
|
110
|
+
fail "Entry point does not verify KB connection"
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
if grep -q "process.exit" src/index.ts; then
|
|
114
|
+
pass "Entry point exits on failure"
|
|
115
|
+
else
|
|
116
|
+
fail "Entry point does not exit on KB failure"
|
|
117
|
+
fi
|
|
118
|
+
else
|
|
119
|
+
warn "src/index.ts not found"
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# ============================================
|
|
123
|
+
# Rule 5: No Fallback Logic
|
|
124
|
+
# ============================================
|
|
125
|
+
echo ""
|
|
126
|
+
echo "Rule 5: No fallback logic..."
|
|
127
|
+
|
|
128
|
+
if [ -d "src/domain" ]; then
|
|
129
|
+
fallbacks=$(grep -rnE "\|\| DEFAULT|\|\| \[|\|\| \{\}" src/domain/ 2>/dev/null || true)
|
|
130
|
+
if [ -n "$fallbacks" ]; then
|
|
131
|
+
fail "Found fallback patterns:"
|
|
132
|
+
echo "$fallbacks" | head -5
|
|
133
|
+
else
|
|
134
|
+
pass "No fallback patterns found"
|
|
135
|
+
fi
|
|
136
|
+
else
|
|
137
|
+
warn "src/domain/ directory not found"
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
# ============================================
|
|
141
|
+
# KB_ENFORCEMENT.md Check
|
|
142
|
+
# ============================================
|
|
143
|
+
echo ""
|
|
144
|
+
echo "KB Enforcement document..."
|
|
145
|
+
|
|
146
|
+
if [ -f "KB_ENFORCEMENT.md" ]; then
|
|
147
|
+
pass "KB_ENFORCEMENT.md present"
|
|
148
|
+
else
|
|
149
|
+
warn "KB_ENFORCEMENT.md not found in project root"
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
# ============================================
|
|
153
|
+
# Summary
|
|
154
|
+
# ============================================
|
|
155
|
+
echo ""
|
|
156
|
+
echo "=== Summary ==="
|
|
157
|
+
|
|
158
|
+
if [ $errors -eq 0 ] && [ $warnings -eq 0 ]; then
|
|
159
|
+
echo -e "${GREEN}All checks passed!${NC}"
|
|
160
|
+
exit 0
|
|
161
|
+
elif [ $errors -eq 0 ]; then
|
|
162
|
+
echo -e "${YELLOW}$warnings warning(s), no errors${NC}"
|
|
163
|
+
exit 0
|
|
164
|
+
else
|
|
165
|
+
echo -e "${RED}$errors error(s), $warnings warning(s)${NC}"
|
|
166
|
+
exit 1
|
|
167
|
+
fi
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KB-First Hooks Command
|
|
3
|
+
*
|
|
4
|
+
* Manages KB-First hooks for enforcement.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
|
|
12
|
+
const HOOK_TEMPLATES = {
|
|
13
|
+
'pre-tool-use.py': `#!/usr/bin/env python3
|
|
14
|
+
"""
|
|
15
|
+
KB-First PreToolUse Hook
|
|
16
|
+
Intercepts Write operations to enforce KB-First patterns.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import sys
|
|
21
|
+
import os
|
|
22
|
+
|
|
23
|
+
def check_kb_first(tool_input):
|
|
24
|
+
"""Check if Write operation follows KB-First principles."""
|
|
25
|
+
if tool_input.get('tool_name') != 'Write':
|
|
26
|
+
return {'decision': 'approve'}
|
|
27
|
+
|
|
28
|
+
file_path = tool_input.get('tool_input', {}).get('file_path', '')
|
|
29
|
+
content = tool_input.get('tool_input', {}).get('content', '')
|
|
30
|
+
|
|
31
|
+
# Check if it's a code file
|
|
32
|
+
code_extensions = ['.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.rs', '.java']
|
|
33
|
+
is_code_file = any(file_path.endswith(ext) for ext in code_extensions)
|
|
34
|
+
|
|
35
|
+
if not is_code_file:
|
|
36
|
+
return {'decision': 'approve'}
|
|
37
|
+
|
|
38
|
+
# Check for KB citation header
|
|
39
|
+
has_kb_citation = 'KB-Generated:' in content or 'Sources:' in content
|
|
40
|
+
|
|
41
|
+
if not has_kb_citation:
|
|
42
|
+
return {
|
|
43
|
+
'decision': 'block',
|
|
44
|
+
'message': 'KB-First Violation: Code files must include KB citation header. Use kb_code_gen first.'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {'decision': 'approve'}
|
|
48
|
+
|
|
49
|
+
if __name__ == '__main__':
|
|
50
|
+
try:
|
|
51
|
+
input_data = json.loads(sys.stdin.read())
|
|
52
|
+
result = check_kb_first(input_data)
|
|
53
|
+
print(json.dumps(result))
|
|
54
|
+
except Exception as e:
|
|
55
|
+
print(json.dumps({'decision': 'approve', 'error': str(e)}))
|
|
56
|
+
`,
|
|
57
|
+
|
|
58
|
+
'post-tool-use.py': `#!/usr/bin/env python3
|
|
59
|
+
"""
|
|
60
|
+
KB-First PostToolUse Hook
|
|
61
|
+
Tracks KB usage and logs gaps.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
import json
|
|
65
|
+
import sys
|
|
66
|
+
import os
|
|
67
|
+
from datetime import datetime
|
|
68
|
+
|
|
69
|
+
def track_kb_usage(tool_result):
|
|
70
|
+
"""Track KB search results and log gaps."""
|
|
71
|
+
tool_name = tool_result.get('tool_name', '')
|
|
72
|
+
|
|
73
|
+
# Track KB searches
|
|
74
|
+
if 'kb_search' in tool_name or 'kb_code_gen' in tool_name:
|
|
75
|
+
result = tool_result.get('tool_result', {})
|
|
76
|
+
coverage = result.get('kb_coverage', 'unknown')
|
|
77
|
+
|
|
78
|
+
if coverage == 'gap':
|
|
79
|
+
log_gap(tool_result)
|
|
80
|
+
|
|
81
|
+
return {'status': 'ok'}
|
|
82
|
+
|
|
83
|
+
def log_gap(tool_result):
|
|
84
|
+
"""Log KB gap for future improvement."""
|
|
85
|
+
gap_log = os.path.join(os.getcwd(), '.ruvector', 'gaps.jsonl')
|
|
86
|
+
|
|
87
|
+
# Ensure directory exists
|
|
88
|
+
os.makedirs(os.path.dirname(gap_log), exist_ok=True)
|
|
89
|
+
|
|
90
|
+
gap_entry = {
|
|
91
|
+
'timestamp': datetime.now().isoformat(),
|
|
92
|
+
'query': tool_result.get('tool_input', {}).get('query', ''),
|
|
93
|
+
'description': tool_result.get('tool_input', {}).get('description', ''),
|
|
94
|
+
'file_path': tool_result.get('tool_input', {}).get('file_path', '')
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
with open(gap_log, 'a') as f:
|
|
98
|
+
f.write(json.dumps(gap_entry) + '\\n')
|
|
99
|
+
|
|
100
|
+
if __name__ == '__main__':
|
|
101
|
+
try:
|
|
102
|
+
input_data = json.loads(sys.stdin.read())
|
|
103
|
+
result = track_kb_usage(input_data)
|
|
104
|
+
print(json.dumps(result))
|
|
105
|
+
except Exception as e:
|
|
106
|
+
print(json.dumps({'status': 'error', 'error': str(e)}))
|
|
107
|
+
`,
|
|
108
|
+
|
|
109
|
+
'session-end.py': `#!/usr/bin/env python3
|
|
110
|
+
"""
|
|
111
|
+
KB-First SessionEnd Hook
|
|
112
|
+
Generates session summary and updates metrics.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
import json
|
|
116
|
+
import sys
|
|
117
|
+
import os
|
|
118
|
+
from datetime import datetime
|
|
119
|
+
|
|
120
|
+
def session_summary(session_data):
|
|
121
|
+
"""Generate KB-First session summary."""
|
|
122
|
+
cwd = os.getcwd()
|
|
123
|
+
summary_file = os.path.join(cwd, '.ruvector', 'sessions.jsonl')
|
|
124
|
+
|
|
125
|
+
# Ensure directory exists
|
|
126
|
+
os.makedirs(os.path.dirname(summary_file), exist_ok=True)
|
|
127
|
+
|
|
128
|
+
# Count KB operations
|
|
129
|
+
kb_searches = session_data.get('kb_searches', 0)
|
|
130
|
+
kb_writes = session_data.get('kb_writes', 0)
|
|
131
|
+
gaps_logged = session_data.get('gaps_logged', 0)
|
|
132
|
+
|
|
133
|
+
summary = {
|
|
134
|
+
'timestamp': datetime.now().isoformat(),
|
|
135
|
+
'duration_minutes': session_data.get('duration_minutes', 0),
|
|
136
|
+
'kb_searches': kb_searches,
|
|
137
|
+
'kb_writes': kb_writes,
|
|
138
|
+
'gaps_logged': gaps_logged,
|
|
139
|
+
'files_modified': session_data.get('files_modified', [])
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
with open(summary_file, 'a') as f:
|
|
143
|
+
f.write(json.dumps(summary) + '\\n')
|
|
144
|
+
|
|
145
|
+
return {'status': 'ok', 'summary': summary}
|
|
146
|
+
|
|
147
|
+
if __name__ == '__main__':
|
|
148
|
+
try:
|
|
149
|
+
input_data = json.loads(sys.stdin.read())
|
|
150
|
+
result = session_summary(input_data)
|
|
151
|
+
print(json.dumps(result))
|
|
152
|
+
except Exception as e:
|
|
153
|
+
print(json.dumps({'status': 'error', 'error': str(e)}))
|
|
154
|
+
`
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export async function hooksCommand(options) {
|
|
158
|
+
const cwd = process.cwd();
|
|
159
|
+
|
|
160
|
+
console.log('');
|
|
161
|
+
console.log(chalk.cyan('KB-First Hooks Manager'));
|
|
162
|
+
console.log('');
|
|
163
|
+
|
|
164
|
+
if (options.install) {
|
|
165
|
+
await installHooks(cwd);
|
|
166
|
+
} else if (options.verify) {
|
|
167
|
+
await verifyHooks(cwd);
|
|
168
|
+
} else if (options.train) {
|
|
169
|
+
await trainHooks(cwd);
|
|
170
|
+
} else if (options.status) {
|
|
171
|
+
await showHookStatus(cwd);
|
|
172
|
+
} else {
|
|
173
|
+
// Default: show status
|
|
174
|
+
await showHookStatus(cwd);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function installHooks(cwd) {
|
|
179
|
+
const spinner = ora('Installing KB-First hooks...').start();
|
|
180
|
+
|
|
181
|
+
const hooksDir = join(cwd, '.ruvector', 'hooks');
|
|
182
|
+
|
|
183
|
+
// Create hooks directory
|
|
184
|
+
if (!existsSync(hooksDir)) {
|
|
185
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Write hook files
|
|
189
|
+
for (const [filename, content] of Object.entries(HOOK_TEMPLATES)) {
|
|
190
|
+
const hookPath = join(hooksDir, filename);
|
|
191
|
+
writeFileSync(hookPath, content);
|
|
192
|
+
chmodSync(hookPath, 0o755);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Update config
|
|
196
|
+
const configPath = join(cwd, '.ruvector', 'config.json');
|
|
197
|
+
if (existsSync(configPath)) {
|
|
198
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
199
|
+
config.hooks = {
|
|
200
|
+
enabled: true,
|
|
201
|
+
preToolUse: true,
|
|
202
|
+
postToolUse: true,
|
|
203
|
+
sessionEnd: true,
|
|
204
|
+
installedAt: new Date().toISOString()
|
|
205
|
+
};
|
|
206
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
spinner.succeed('Hooks installed successfully');
|
|
210
|
+
|
|
211
|
+
console.log('');
|
|
212
|
+
console.log(chalk.white('Installed hooks:'));
|
|
213
|
+
console.log(chalk.gray(' - pre-tool-use.py (KB citation enforcement)'));
|
|
214
|
+
console.log(chalk.gray(' - post-tool-use.py (Gap logging)'));
|
|
215
|
+
console.log(chalk.gray(' - session-end.py (Session summary)'));
|
|
216
|
+
console.log('');
|
|
217
|
+
console.log(chalk.yellow('Note: Add hooks to Claude Code settings.json:'));
|
|
218
|
+
console.log(chalk.gray(`
|
|
219
|
+
"hooks": {
|
|
220
|
+
"PreToolUse": [
|
|
221
|
+
{
|
|
222
|
+
"matcher": "Write",
|
|
223
|
+
"hooks": [".ruvector/hooks/pre-tool-use.py"]
|
|
224
|
+
}
|
|
225
|
+
],
|
|
226
|
+
"PostToolUse": [".ruvector/hooks/post-tool-use.py"],
|
|
227
|
+
"SessionEnd": [".ruvector/hooks/session-end.py"]
|
|
228
|
+
}
|
|
229
|
+
`));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function verifyHooks(cwd) {
|
|
233
|
+
console.log(chalk.white('Verifying hooks installation...'));
|
|
234
|
+
console.log('');
|
|
235
|
+
|
|
236
|
+
const hooksDir = join(cwd, '.ruvector', 'hooks');
|
|
237
|
+
const requiredHooks = ['pre-tool-use.py', 'post-tool-use.py', 'session-end.py'];
|
|
238
|
+
|
|
239
|
+
let passed = 0;
|
|
240
|
+
let failed = 0;
|
|
241
|
+
|
|
242
|
+
for (const hook of requiredHooks) {
|
|
243
|
+
const hookPath = join(hooksDir, hook);
|
|
244
|
+
|
|
245
|
+
if (existsSync(hookPath)) {
|
|
246
|
+
// Check if executable
|
|
247
|
+
try {
|
|
248
|
+
const { statSync } = await import('fs');
|
|
249
|
+
const stats = statSync(hookPath);
|
|
250
|
+
const isExecutable = (stats.mode & 0o111) !== 0;
|
|
251
|
+
|
|
252
|
+
if (isExecutable) {
|
|
253
|
+
console.log(chalk.green(` ✅ ${hook}`));
|
|
254
|
+
passed++;
|
|
255
|
+
} else {
|
|
256
|
+
console.log(chalk.yellow(` ⚠️ ${hook} (not executable)`));
|
|
257
|
+
failed++;
|
|
258
|
+
}
|
|
259
|
+
} catch {
|
|
260
|
+
console.log(chalk.red(` ❌ ${hook} (error checking)`));
|
|
261
|
+
failed++;
|
|
262
|
+
}
|
|
263
|
+
} else {
|
|
264
|
+
console.log(chalk.red(` ❌ ${hook} (not found)`));
|
|
265
|
+
failed++;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.log('');
|
|
270
|
+
if (failed === 0) {
|
|
271
|
+
console.log(chalk.green('All hooks verified successfully.'));
|
|
272
|
+
} else {
|
|
273
|
+
console.log(chalk.yellow(`${failed} hook(s) need attention.`));
|
|
274
|
+
console.log(chalk.gray('Run: kb-first hooks --install'));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function trainHooks(cwd) {
|
|
279
|
+
const spinner = ora('Pre-training hooks with KB-First patterns...').start();
|
|
280
|
+
|
|
281
|
+
// Create training data for ReasoningBank
|
|
282
|
+
const trainingPatterns = [
|
|
283
|
+
{
|
|
284
|
+
pattern: 'KB citation required for code files',
|
|
285
|
+
trigger: 'Write to .ts, .tsx, .js, .jsx, .py files',
|
|
286
|
+
action: 'Check for KB-Generated: or Sources: header',
|
|
287
|
+
consequence: 'Block if missing KB citation'
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
pattern: 'Gap logging on KB search',
|
|
291
|
+
trigger: 'kb_search or kb_code_gen returns coverage=gap',
|
|
292
|
+
action: 'Log to .ruvector/gaps.jsonl',
|
|
293
|
+
consequence: 'Track for future KB improvement'
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
pattern: 'Session summary on end',
|
|
297
|
+
trigger: 'Session ends',
|
|
298
|
+
action: 'Write summary to .ruvector/sessions.jsonl',
|
|
299
|
+
consequence: 'Track KB usage metrics'
|
|
300
|
+
}
|
|
301
|
+
];
|
|
302
|
+
|
|
303
|
+
const patternsPath = join(cwd, '.ruvector', 'training-patterns.json');
|
|
304
|
+
writeFileSync(patternsPath, JSON.stringify(trainingPatterns, null, 2));
|
|
305
|
+
|
|
306
|
+
spinner.succeed('Hooks pre-trained with KB-First patterns');
|
|
307
|
+
|
|
308
|
+
console.log('');
|
|
309
|
+
console.log(chalk.white('Training patterns installed:'));
|
|
310
|
+
for (const pattern of trainingPatterns) {
|
|
311
|
+
console.log(chalk.gray(` - ${pattern.pattern}`));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function showHookStatus(cwd) {
|
|
316
|
+
const configPath = join(cwd, '.ruvector', 'config.json');
|
|
317
|
+
const hooksDir = join(cwd, '.ruvector', 'hooks');
|
|
318
|
+
|
|
319
|
+
if (!existsSync(configPath)) {
|
|
320
|
+
console.log(chalk.red('Not a KB-First project.'));
|
|
321
|
+
console.log(chalk.gray('Run: kb-first init'));
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
326
|
+
const hooksConfig = config.hooks || {};
|
|
327
|
+
|
|
328
|
+
console.log(chalk.white('Hook Status:'));
|
|
329
|
+
console.log('');
|
|
330
|
+
console.log(` Enabled: ${hooksConfig.enabled ? chalk.green('Yes') : chalk.red('No')}`);
|
|
331
|
+
console.log(` PreToolUse: ${hooksConfig.preToolUse ? chalk.green('Active') : chalk.gray('Inactive')}`);
|
|
332
|
+
console.log(` PostToolUse: ${hooksConfig.postToolUse ? chalk.green('Active') : chalk.gray('Inactive')}`);
|
|
333
|
+
console.log(` SessionEnd: ${hooksConfig.sessionEnd ? chalk.green('Active') : chalk.gray('Inactive')}`);
|
|
334
|
+
|
|
335
|
+
if (hooksConfig.installedAt) {
|
|
336
|
+
console.log(` Installed: ${chalk.gray(hooksConfig.installedAt)}`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
console.log('');
|
|
340
|
+
|
|
341
|
+
// Check files
|
|
342
|
+
const hookFiles = ['pre-tool-use.py', 'post-tool-use.py', 'session-end.py'];
|
|
343
|
+
const foundHooks = hookFiles.filter(f => existsSync(join(hooksDir, f)));
|
|
344
|
+
|
|
345
|
+
console.log(chalk.white('Hook Files:'));
|
|
346
|
+
console.log(` ${foundHooks.length}/${hookFiles.length} hooks installed`);
|
|
347
|
+
|
|
348
|
+
// Show gaps count if any
|
|
349
|
+
const gapsPath = join(cwd, '.ruvector', 'gaps.jsonl');
|
|
350
|
+
if (existsSync(gapsPath)) {
|
|
351
|
+
const gapsContent = readFileSync(gapsPath, 'utf-8').trim();
|
|
352
|
+
const gapCount = gapsContent ? gapsContent.split('\n').length : 0;
|
|
353
|
+
console.log(` ${chalk.yellow(gapCount)} KB gaps logged`);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
console.log('');
|
|
357
|
+
console.log(chalk.gray('Commands:'));
|
|
358
|
+
console.log(chalk.gray(' kb-first hooks --install Install hooks'));
|
|
359
|
+
console.log(chalk.gray(' kb-first hooks --verify Verify installation'));
|
|
360
|
+
console.log(chalk.gray(' kb-first hooks --train Pre-train with patterns'));
|
|
361
|
+
}
|