cf-doctor 1.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 +161 -0
- package/bin/cf-doctor.js +18 -0
- package/dist/doctor.d.ts +21 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +33 -0
- package/dist/doctor.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server.d.ts +12 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +200 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/patches/apply.d.ts +7 -0
- package/dist/patches/apply.d.ts.map +1 -0
- package/dist/patches/apply.js +51 -0
- package/dist/patches/apply.js.map +1 -0
- package/dist/persistence/episodes.d.ts +42 -0
- package/dist/persistence/episodes.d.ts.map +1 -0
- package/dist/persistence/episodes.js +160 -0
- package/dist/persistence/episodes.js.map +1 -0
- package/dist/persistence/index.d.ts +4 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +4 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/q-table.d.ts +42 -0
- package/dist/persistence/q-table.d.ts.map +1 -0
- package/dist/persistence/q-table.js +138 -0
- package/dist/persistence/q-table.js.map +1 -0
- package/dist/persistence/sona.d.ts +45 -0
- package/dist/persistence/sona.d.ts.map +1 -0
- package/dist/persistence/sona.js +142 -0
- package/dist/persistence/sona.js.map +1 -0
- package/package.json +63 -0
- package/patches/README.md +68 -0
- package/patches/neural-index.patch +8 -0
- package/patches/quick-test.patch +25 -0
- package/patches/sona-integration.patch +76 -0
- package/patches/version-bridge.patch +30 -0
- package/scripts/cf-doctor.sh +684 -0
- package/tests/run-all-tests.sh +32 -0
- package/tests/test-01-doctor-passes.sh +43 -0
- package/tests/test-02-mcp-init.sh +36 -0
- package/tests/test-03-agent-spawn-no-ruvector.sh +84 -0
- package/tests/test-04-agent-spawn-with-ruvector.sh +37 -0
- package/tests/test-05-learning-persists.sh +94 -0
- package/tests/test-06-hooks-version-bridge.sh +82 -0
- package/tests/test-helpers.sh +88 -0
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
###############################################################################
|
|
5
|
+
# cf-doctor.sh — Setup validation and auto-fix for claude-flow + ruvector
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# ./cf-doctor.sh # Check-only mode
|
|
9
|
+
# ./cf-doctor.sh --fix # Auto-install missing packages, create dirs
|
|
10
|
+
# ./cf-doctor.sh --json # Machine-readable JSON output
|
|
11
|
+
# ./cf-doctor.sh --fix --json # Both
|
|
12
|
+
#
|
|
13
|
+
# Exit codes:
|
|
14
|
+
# 0 — All checks passed (GREEN or YELLOW only)
|
|
15
|
+
# 1 — One or more RED failures detected
|
|
16
|
+
###############################################################################
|
|
17
|
+
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# Color codes and symbols
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
GREEN='\033[32m'
|
|
22
|
+
YELLOW='\033[33m'
|
|
23
|
+
RED='\033[31m'
|
|
24
|
+
BOLD='\033[1m'
|
|
25
|
+
DIM='\033[2m'
|
|
26
|
+
RESET='\033[0m'
|
|
27
|
+
|
|
28
|
+
SYM_OK="✅"
|
|
29
|
+
SYM_WARN="⚠️"
|
|
30
|
+
SYM_FAIL="❌"
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Globals
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
FIX_MODE=false
|
|
36
|
+
JSON_MODE=false
|
|
37
|
+
RED_COUNT=0
|
|
38
|
+
YELLOW_COUNT=0
|
|
39
|
+
GREEN_COUNT=0
|
|
40
|
+
|
|
41
|
+
# Parallel arrays for summary table
|
|
42
|
+
declare -a SUMMARY_SYMBOLS=()
|
|
43
|
+
declare -a SUMMARY_LABELS=()
|
|
44
|
+
declare -a SUMMARY_COLORS=()
|
|
45
|
+
|
|
46
|
+
# JSON accumulator (parallel arrays for bash 3 compatibility)
|
|
47
|
+
JSON_CHECK_KEYS=()
|
|
48
|
+
JSON_CHECK_VALS=()
|
|
49
|
+
|
|
50
|
+
# Detected values for config generation
|
|
51
|
+
DB_DRIVER=""
|
|
52
|
+
CF_VERSION=""
|
|
53
|
+
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
# Argument parsing
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
for arg in "$@"; do
|
|
58
|
+
case "$arg" in
|
|
59
|
+
--fix) FIX_MODE=true ;;
|
|
60
|
+
--json) JSON_MODE=true ;;
|
|
61
|
+
--help|-h)
|
|
62
|
+
echo "Usage: $0 [--fix] [--json]"
|
|
63
|
+
echo ""
|
|
64
|
+
echo " --fix Auto-install missing packages and create directories"
|
|
65
|
+
echo " --json Output machine-readable JSON instead of tables"
|
|
66
|
+
echo ""
|
|
67
|
+
exit 0
|
|
68
|
+
;;
|
|
69
|
+
*)
|
|
70
|
+
echo "Unknown argument: $arg" >&2
|
|
71
|
+
echo "Usage: $0 [--fix] [--json]" >&2
|
|
72
|
+
exit 2
|
|
73
|
+
;;
|
|
74
|
+
esac
|
|
75
|
+
done
|
|
76
|
+
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
# Helpers
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
log_green() {
|
|
81
|
+
if ! $JSON_MODE; then
|
|
82
|
+
printf " ${GREEN}${SYM_OK} %s${RESET}\n" "$1"
|
|
83
|
+
fi
|
|
84
|
+
SUMMARY_SYMBOLS+=("$SYM_OK")
|
|
85
|
+
SUMMARY_LABELS+=("$1")
|
|
86
|
+
SUMMARY_COLORS+=("GREEN")
|
|
87
|
+
GREEN_COUNT=$((GREEN_COUNT + 1))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
log_yellow() {
|
|
91
|
+
if ! $JSON_MODE; then
|
|
92
|
+
printf " ${YELLOW}${SYM_WARN} %s${RESET}\n" "$1"
|
|
93
|
+
fi
|
|
94
|
+
SUMMARY_SYMBOLS+=("$SYM_WARN")
|
|
95
|
+
SUMMARY_LABELS+=("$1")
|
|
96
|
+
SUMMARY_COLORS+=("YELLOW")
|
|
97
|
+
YELLOW_COUNT=$((YELLOW_COUNT + 1))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
log_red() {
|
|
101
|
+
if ! $JSON_MODE; then
|
|
102
|
+
printf " ${RED}${SYM_FAIL} %s${RESET}\n" "$1"
|
|
103
|
+
fi
|
|
104
|
+
SUMMARY_SYMBOLS+=("$SYM_FAIL")
|
|
105
|
+
SUMMARY_LABELS+=("$1")
|
|
106
|
+
SUMMARY_COLORS+=("RED")
|
|
107
|
+
RED_COUNT=$((RED_COUNT + 1))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
section_header() {
|
|
111
|
+
if ! $JSON_MODE; then
|
|
112
|
+
printf "\n${BOLD}── %s ──${RESET}\n" "$1"
|
|
113
|
+
fi
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
json_set() {
|
|
117
|
+
# json_set <key> <status> <detail>
|
|
118
|
+
JSON_CHECK_KEYS+=("$1")
|
|
119
|
+
JSON_CHECK_VALS+=("{\"status\":\"$2\",\"detail\":$(json_escape_string "$3")}")
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
json_escape_string() {
|
|
123
|
+
# Minimal JSON string escaping
|
|
124
|
+
local s="$1"
|
|
125
|
+
s="${s//\\/\\\\}"
|
|
126
|
+
s="${s//\"/\\\"}"
|
|
127
|
+
s="${s//$'\n'/\\n}"
|
|
128
|
+
s="${s//$'\r'/\\r}"
|
|
129
|
+
s="${s//$'\t'/\\t}"
|
|
130
|
+
printf '"%s"' "$s"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Compare semver: returns 0 if $1 >= $2
|
|
134
|
+
semver_gte() {
|
|
135
|
+
local IFS='.'
|
|
136
|
+
local -a v1=($1) v2=($2)
|
|
137
|
+
for i in 0 1 2; do
|
|
138
|
+
local a="${v1[$i]:-0}"
|
|
139
|
+
local b="${v2[$i]:-0}"
|
|
140
|
+
# Strip non-numeric suffixes (e.g., "20.11.0-rc1" -> "20")
|
|
141
|
+
a="${a%%[!0-9]*}"
|
|
142
|
+
b="${b%%[!0-9]*}"
|
|
143
|
+
a="${a:-0}"
|
|
144
|
+
b="${b:-0}"
|
|
145
|
+
if (( a > b )); then return 0; fi
|
|
146
|
+
if (( a < b )); then return 1; fi
|
|
147
|
+
done
|
|
148
|
+
return 0
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# ---------------------------------------------------------------------------
|
|
152
|
+
# Section 1: Prerequisites
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
check_prerequisites() {
|
|
155
|
+
section_header "Prerequisites"
|
|
156
|
+
|
|
157
|
+
# --- Node.js ---
|
|
158
|
+
if command -v node &>/dev/null; then
|
|
159
|
+
local node_ver
|
|
160
|
+
node_ver="$(node --version 2>/dev/null | sed 's/^v//')"
|
|
161
|
+
if semver_gte "$node_ver" "20.0.0"; then
|
|
162
|
+
log_green "Node.js ${node_ver}"
|
|
163
|
+
json_set "nodejs" "green" "Node.js ${node_ver}"
|
|
164
|
+
else
|
|
165
|
+
log_yellow "Node.js ${node_ver} (< 20 — upgrade recommended)"
|
|
166
|
+
json_set "nodejs" "yellow" "Node.js ${node_ver} — below minimum 20.x"
|
|
167
|
+
if $FIX_MODE; then
|
|
168
|
+
if ! $JSON_MODE; then
|
|
169
|
+
printf " ${DIM}Hint: Use nvm or fnm to install Node 20+${RESET}\n"
|
|
170
|
+
printf " ${DIM} curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash${RESET}\n"
|
|
171
|
+
printf " ${DIM} nvm install 20${RESET}\n"
|
|
172
|
+
fi
|
|
173
|
+
fi
|
|
174
|
+
fi
|
|
175
|
+
else
|
|
176
|
+
log_red "Node.js not found"
|
|
177
|
+
json_set "nodejs" "red" "Node.js not installed"
|
|
178
|
+
if $FIX_MODE && ! $JSON_MODE; then
|
|
179
|
+
printf " ${DIM}Install Node.js 20+: https://nodejs.org/${RESET}\n"
|
|
180
|
+
fi
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
# --- pnpm ---
|
|
184
|
+
if command -v pnpm &>/dev/null; then
|
|
185
|
+
local pnpm_ver
|
|
186
|
+
pnpm_ver="$(pnpm --version 2>/dev/null)"
|
|
187
|
+
if semver_gte "$pnpm_ver" "8.0.0"; then
|
|
188
|
+
log_green "pnpm ${pnpm_ver}"
|
|
189
|
+
json_set "pnpm" "green" "pnpm ${pnpm_ver}"
|
|
190
|
+
else
|
|
191
|
+
log_yellow "pnpm ${pnpm_ver} (< 8 — upgrade recommended)"
|
|
192
|
+
json_set "pnpm" "yellow" "pnpm ${pnpm_ver} — below minimum 8.x"
|
|
193
|
+
fi
|
|
194
|
+
else
|
|
195
|
+
log_yellow "pnpm not installed (optional, recommend: npm i -g pnpm)"
|
|
196
|
+
json_set "pnpm" "yellow" "pnpm not installed"
|
|
197
|
+
if $FIX_MODE; then
|
|
198
|
+
if ! $JSON_MODE; then
|
|
199
|
+
printf " ${DIM}Installing pnpm...${RESET}\n"
|
|
200
|
+
fi
|
|
201
|
+
if npm i -g pnpm &>/dev/null; then
|
|
202
|
+
local new_pnpm_ver
|
|
203
|
+
new_pnpm_ver="$(pnpm --version 2>/dev/null || echo 'unknown')"
|
|
204
|
+
log_green "pnpm ${new_pnpm_ver} (auto-installed)"
|
|
205
|
+
json_set "pnpm" "green" "pnpm ${new_pnpm_ver} auto-installed"
|
|
206
|
+
else
|
|
207
|
+
if ! $JSON_MODE; then
|
|
208
|
+
printf " ${RED}Failed to install pnpm via npm${RESET}\n"
|
|
209
|
+
fi
|
|
210
|
+
fi
|
|
211
|
+
fi
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
# --- Rust toolchain ---
|
|
215
|
+
if command -v rustc &>/dev/null; then
|
|
216
|
+
local rust_ver
|
|
217
|
+
rust_ver="$(rustc --version 2>/dev/null | awk '{print $2}')"
|
|
218
|
+
log_green "Rust ${rust_ver}"
|
|
219
|
+
json_set "rust" "green" "Rust ${rust_ver}"
|
|
220
|
+
else
|
|
221
|
+
log_yellow "Rust not installed (optional — needed only for ruvector source builds)"
|
|
222
|
+
json_set "rust" "yellow" "Rust not installed (optional)"
|
|
223
|
+
fi
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
# ---------------------------------------------------------------------------
|
|
227
|
+
# Section 2: Installation Validation
|
|
228
|
+
# ---------------------------------------------------------------------------
|
|
229
|
+
check_installation() {
|
|
230
|
+
section_header "Installation Validation"
|
|
231
|
+
|
|
232
|
+
# --- helper: portable timeout ---
|
|
233
|
+
# macOS may lack coreutils timeout; fall back to perl or plain exec
|
|
234
|
+
_run_with_timeout() {
|
|
235
|
+
local secs="$1"; shift
|
|
236
|
+
if command -v timeout &>/dev/null; then
|
|
237
|
+
timeout "$secs" "$@" 2>/dev/null || true
|
|
238
|
+
elif command -v perl &>/dev/null; then
|
|
239
|
+
perl -e "alarm $secs; exec @ARGV" -- "$@" 2>/dev/null || true
|
|
240
|
+
else
|
|
241
|
+
"$@" 2>/dev/null || true
|
|
242
|
+
fi
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# --- @claude-flow/cli ---
|
|
246
|
+
local cf_found=false
|
|
247
|
+
|
|
248
|
+
# Try npx claude-flow --version first (may not be installed)
|
|
249
|
+
CF_VERSION="$(_run_with_timeout 10 npx claude-flow --version)" || true
|
|
250
|
+
if [[ -n "$CF_VERSION" ]]; then
|
|
251
|
+
cf_found=true
|
|
252
|
+
log_green "@claude-flow/cli ${CF_VERSION}"
|
|
253
|
+
json_set "claude_flow_cli" "green" "@claude-flow/cli ${CF_VERSION}"
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
# Fallback: check node_modules
|
|
257
|
+
if ! $cf_found; then
|
|
258
|
+
local pnpm_path=""
|
|
259
|
+
pnpm_path="$(find . node_modules -maxdepth 5 -path '*/@claude-flow/cli/package.json' 2>/dev/null | head -1)" || true
|
|
260
|
+
if [[ -n "$pnpm_path" ]]; then
|
|
261
|
+
local pkg_ver
|
|
262
|
+
pkg_ver="$(node -e "console.log(require('./${pnpm_path}').version)" 2>/dev/null)" || pkg_ver="unknown"
|
|
263
|
+
cf_found=true
|
|
264
|
+
CF_VERSION="$pkg_ver"
|
|
265
|
+
log_green "@claude-flow/cli ${pkg_ver} (found in node_modules)"
|
|
266
|
+
json_set "claude_flow_cli" "green" "@claude-flow/cli ${pkg_ver} found in node_modules"
|
|
267
|
+
fi
|
|
268
|
+
fi
|
|
269
|
+
|
|
270
|
+
if ! $cf_found; then
|
|
271
|
+
log_red "@claude-flow/cli not found"
|
|
272
|
+
json_set "claude_flow_cli" "red" "Not installed"
|
|
273
|
+
if $FIX_MODE; then
|
|
274
|
+
if ! $JSON_MODE; then
|
|
275
|
+
printf " ${DIM}Installing @claude-flow/cli...${RESET}\n"
|
|
276
|
+
fi
|
|
277
|
+
local install_ok=false
|
|
278
|
+
npm install -g @anthropic-ai/claude-flow &>/dev/null && install_ok=true
|
|
279
|
+
if ! $install_ok; then
|
|
280
|
+
npm install @claude-flow/cli &>/dev/null && install_ok=true
|
|
281
|
+
fi
|
|
282
|
+
if $install_ok; then
|
|
283
|
+
CF_VERSION="$(_run_with_timeout 10 npx claude-flow --version)" || CF_VERSION="installed"
|
|
284
|
+
log_green "@claude-flow/cli ${CF_VERSION} (auto-installed)"
|
|
285
|
+
json_set "claude_flow_cli" "green" "Auto-installed ${CF_VERSION}"
|
|
286
|
+
else
|
|
287
|
+
if ! $JSON_MODE; then
|
|
288
|
+
printf " ${RED}Auto-install failed. Try: npm install -g @claude-flow/cli${RESET}\n"
|
|
289
|
+
fi
|
|
290
|
+
fi
|
|
291
|
+
fi
|
|
292
|
+
fi
|
|
293
|
+
|
|
294
|
+
# --- MCP handshake ---
|
|
295
|
+
local mcp_request='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"doctor","version":"1.0"}}}'
|
|
296
|
+
local mcp_response=""
|
|
297
|
+
|
|
298
|
+
if command -v npx &>/dev/null; then
|
|
299
|
+
mcp_response="$(echo "$mcp_request" | _run_with_timeout 8 npx claude-flow mcp)" || true
|
|
300
|
+
fi
|
|
301
|
+
|
|
302
|
+
if [[ "$mcp_response" == *'"result"'* ]]; then
|
|
303
|
+
log_green "MCP handshake OK"
|
|
304
|
+
json_set "mcp_handshake" "green" "MCP initialize response received"
|
|
305
|
+
elif [[ -n "$mcp_response" ]]; then
|
|
306
|
+
log_yellow "MCP responded but no 'result' field (partial handshake)"
|
|
307
|
+
json_set "mcp_handshake" "yellow" "Partial response: ${mcp_response:0:120}"
|
|
308
|
+
else
|
|
309
|
+
log_red "MCP handshake failed (timeout or no response)"
|
|
310
|
+
json_set "mcp_handshake" "red" "No response from claude-flow mcp"
|
|
311
|
+
fi
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
# ---------------------------------------------------------------------------
|
|
315
|
+
# Section 3: @ruvector Package Detection
|
|
316
|
+
# ---------------------------------------------------------------------------
|
|
317
|
+
check_ruvector() {
|
|
318
|
+
section_header "@ruvector Package Detection"
|
|
319
|
+
|
|
320
|
+
local -a packages=(
|
|
321
|
+
"@ruvector/core"
|
|
322
|
+
"@ruvector/sona"
|
|
323
|
+
"@ruvector/attention"
|
|
324
|
+
"@ruvector/memory"
|
|
325
|
+
"@ruvector/swarm"
|
|
326
|
+
"@ruvector/neural"
|
|
327
|
+
"@ruvector/embed"
|
|
328
|
+
"@ruvector/vector"
|
|
329
|
+
)
|
|
330
|
+
local installed_count=0
|
|
331
|
+
local total=${#packages[@]}
|
|
332
|
+
local -a missing_packages=()
|
|
333
|
+
|
|
334
|
+
for pkg in "${packages[@]}"; do
|
|
335
|
+
# Try to resolve the package
|
|
336
|
+
if node -e "require.resolve('${pkg}')" &>/dev/null; then
|
|
337
|
+
installed_count=$((installed_count + 1))
|
|
338
|
+
json_set "ruvector_${pkg##*/}" "green" "Installed"
|
|
339
|
+
else
|
|
340
|
+
missing_packages+=("$pkg")
|
|
341
|
+
json_set "ruvector_${pkg##*/}" "yellow" "Not installed (mock fallback)"
|
|
342
|
+
fi
|
|
343
|
+
done
|
|
344
|
+
|
|
345
|
+
if (( installed_count == total )); then
|
|
346
|
+
log_green "${installed_count}/${total} @ruvector packages installed"
|
|
347
|
+
json_set "ruvector_summary" "green" "All ${total} packages installed"
|
|
348
|
+
elif (( installed_count > 0 )); then
|
|
349
|
+
log_yellow "${installed_count}/${total} @ruvector packages (mocks for rest)"
|
|
350
|
+
json_set "ruvector_summary" "yellow" "${installed_count}/${total} installed"
|
|
351
|
+
if ! $JSON_MODE; then
|
|
352
|
+
printf " ${DIM}Missing: %s${RESET}\n" "${missing_packages[*]}"
|
|
353
|
+
fi
|
|
354
|
+
else
|
|
355
|
+
log_yellow "0/${total} @ruvector packages (all using mock fallback)"
|
|
356
|
+
json_set "ruvector_summary" "yellow" "0/${total} — all mocked"
|
|
357
|
+
fi
|
|
358
|
+
|
|
359
|
+
if $FIX_MODE && (( ${#missing_packages[@]} > 0 )); then
|
|
360
|
+
if ! $JSON_MODE; then
|
|
361
|
+
printf " ${DIM}Attempting to install missing @ruvector packages...${RESET}\n"
|
|
362
|
+
fi
|
|
363
|
+
local fix_count=0
|
|
364
|
+
for pkg in "${missing_packages[@]}"; do
|
|
365
|
+
if npm install "$pkg" &>/dev/null; then
|
|
366
|
+
fix_count=$((fix_count + 1))
|
|
367
|
+
fi
|
|
368
|
+
done
|
|
369
|
+
if (( fix_count > 0 )); then
|
|
370
|
+
installed_count=$((installed_count + fix_count))
|
|
371
|
+
if ! $JSON_MODE; then
|
|
372
|
+
printf " ${GREEN}Installed ${fix_count} additional packages${RESET}\n"
|
|
373
|
+
fi
|
|
374
|
+
fi
|
|
375
|
+
fi
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
# ---------------------------------------------------------------------------
|
|
379
|
+
# Section 4: Directory Auto-Creation
|
|
380
|
+
# ---------------------------------------------------------------------------
|
|
381
|
+
check_directories() {
|
|
382
|
+
section_header "Directory Structure"
|
|
383
|
+
|
|
384
|
+
local -a dirs=(
|
|
385
|
+
".claude-flow/agents"
|
|
386
|
+
".claude-flow/memory"
|
|
387
|
+
".claude-flow/sessions"
|
|
388
|
+
".claude-flow/learning"
|
|
389
|
+
)
|
|
390
|
+
local all_exist=true
|
|
391
|
+
local created_count=0
|
|
392
|
+
|
|
393
|
+
for dir in "${dirs[@]}"; do
|
|
394
|
+
if [[ -d "$dir" ]]; then
|
|
395
|
+
json_set "dir_${dir//\//_}" "green" "Exists"
|
|
396
|
+
else
|
|
397
|
+
all_exist=false
|
|
398
|
+
if $FIX_MODE || true; then
|
|
399
|
+
# Always create missing directories (non-destructive)
|
|
400
|
+
mkdir -p "$dir"
|
|
401
|
+
created_count=$((created_count + 1))
|
|
402
|
+
json_set "dir_${dir//\//_}" "green" "Created"
|
|
403
|
+
if ! $JSON_MODE; then
|
|
404
|
+
printf " ${DIM}Created: ${dir}${RESET}\n"
|
|
405
|
+
fi
|
|
406
|
+
fi
|
|
407
|
+
fi
|
|
408
|
+
done
|
|
409
|
+
|
|
410
|
+
if $all_exist; then
|
|
411
|
+
log_green "Directories OK"
|
|
412
|
+
json_set "directories" "green" "All directories present"
|
|
413
|
+
elif (( created_count > 0 )); then
|
|
414
|
+
log_green "Directories OK (created ${created_count} missing)"
|
|
415
|
+
json_set "directories" "green" "Created ${created_count} missing directories"
|
|
416
|
+
else
|
|
417
|
+
log_yellow "Some directories missing (run with --fix to create)"
|
|
418
|
+
json_set "directories" "yellow" "Missing directories"
|
|
419
|
+
fi
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
# ---------------------------------------------------------------------------
|
|
423
|
+
# Section 5: Database Layer
|
|
424
|
+
# ---------------------------------------------------------------------------
|
|
425
|
+
check_database() {
|
|
426
|
+
section_header "Database Layer"
|
|
427
|
+
|
|
428
|
+
local native_ok=false
|
|
429
|
+
local wasm_ok=false
|
|
430
|
+
|
|
431
|
+
# Check better-sqlite3
|
|
432
|
+
if node -e "require('better-sqlite3')" &>/dev/null; then
|
|
433
|
+
native_ok=true
|
|
434
|
+
DB_DRIVER="better-sqlite3"
|
|
435
|
+
fi
|
|
436
|
+
|
|
437
|
+
# Check sql.js WASM fallback
|
|
438
|
+
if node -e "require('sql.js')" &>/dev/null; then
|
|
439
|
+
wasm_ok=true
|
|
440
|
+
if [[ -z "$DB_DRIVER" ]]; then
|
|
441
|
+
DB_DRIVER="sql.js"
|
|
442
|
+
fi
|
|
443
|
+
fi
|
|
444
|
+
|
|
445
|
+
if $native_ok; then
|
|
446
|
+
log_green "Database: better-sqlite3 (native)"
|
|
447
|
+
json_set "database" "green" "better-sqlite3 native module"
|
|
448
|
+
elif $wasm_ok; then
|
|
449
|
+
log_yellow "Database: sql.js WASM (native better-sqlite3 unavailable)"
|
|
450
|
+
json_set "database" "yellow" "sql.js WASM fallback only"
|
|
451
|
+
if $FIX_MODE; then
|
|
452
|
+
if ! $JSON_MODE; then
|
|
453
|
+
printf " ${DIM}Attempting to install better-sqlite3...${RESET}\n"
|
|
454
|
+
fi
|
|
455
|
+
if npm install better-sqlite3 &>/dev/null; then
|
|
456
|
+
if node -e "require('better-sqlite3')" &>/dev/null; then
|
|
457
|
+
DB_DRIVER="better-sqlite3"
|
|
458
|
+
log_green "Database: better-sqlite3 (auto-installed)"
|
|
459
|
+
json_set "database" "green" "better-sqlite3 auto-installed"
|
|
460
|
+
fi
|
|
461
|
+
fi
|
|
462
|
+
fi
|
|
463
|
+
else
|
|
464
|
+
log_red "No database driver found (neither better-sqlite3 nor sql.js)"
|
|
465
|
+
json_set "database" "red" "No database driver available"
|
|
466
|
+
if $FIX_MODE; then
|
|
467
|
+
if ! $JSON_MODE; then
|
|
468
|
+
printf " ${DIM}Attempting to install sql.js...${RESET}\n"
|
|
469
|
+
fi
|
|
470
|
+
if npm install sql.js &>/dev/null; then
|
|
471
|
+
if node -e "require('sql.js')" &>/dev/null; then
|
|
472
|
+
DB_DRIVER="sql.js"
|
|
473
|
+
log_green "Database: sql.js (auto-installed)"
|
|
474
|
+
json_set "database" "green" "sql.js auto-installed"
|
|
475
|
+
fi
|
|
476
|
+
else
|
|
477
|
+
if ! $JSON_MODE; then
|
|
478
|
+
printf " ${RED}Failed to install database drivers${RESET}\n"
|
|
479
|
+
printf " ${DIM}Try manually: npm install better-sqlite3 || npm install sql.js${RESET}\n"
|
|
480
|
+
fi
|
|
481
|
+
fi
|
|
482
|
+
fi
|
|
483
|
+
fi
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
# ---------------------------------------------------------------------------
|
|
487
|
+
# Section 6: Config Generation
|
|
488
|
+
# ---------------------------------------------------------------------------
|
|
489
|
+
check_config() {
|
|
490
|
+
section_header "Configuration"
|
|
491
|
+
|
|
492
|
+
local config_path=".claude-flow/config.yaml"
|
|
493
|
+
|
|
494
|
+
if [[ -f "$config_path" ]]; then
|
|
495
|
+
log_green "Config exists (${config_path})"
|
|
496
|
+
json_set "config" "green" "Config file present at ${config_path}"
|
|
497
|
+
else
|
|
498
|
+
# Determine which DB driver to configure
|
|
499
|
+
local driver="${DB_DRIVER:-sql.js}"
|
|
500
|
+
|
|
501
|
+
if $FIX_MODE || true; then
|
|
502
|
+
# Config generation is non-destructive, always do it if missing
|
|
503
|
+
mkdir -p "$(dirname "$config_path")"
|
|
504
|
+
cat > "$config_path" <<CFEOF
|
|
505
|
+
version: 3
|
|
506
|
+
database:
|
|
507
|
+
driver: ${driver}
|
|
508
|
+
path: .claude-flow/memory/claude-flow.db
|
|
509
|
+
agents:
|
|
510
|
+
maxConcurrent: 4
|
|
511
|
+
defaultModel: claude-sonnet-4-20250514
|
|
512
|
+
memory:
|
|
513
|
+
persistence: true
|
|
514
|
+
learningDir: .claude-flow/learning
|
|
515
|
+
mcp:
|
|
516
|
+
enabled: true
|
|
517
|
+
transport: stdio
|
|
518
|
+
CFEOF
|
|
519
|
+
log_green "Config generated (${config_path}, driver: ${driver})"
|
|
520
|
+
json_set "config" "green" "Config generated with driver=${driver}"
|
|
521
|
+
else
|
|
522
|
+
log_yellow "Config missing (${config_path}) — run with --fix to generate"
|
|
523
|
+
json_set "config" "yellow" "Config file missing"
|
|
524
|
+
fi
|
|
525
|
+
fi
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
# ---------------------------------------------------------------------------
|
|
529
|
+
# Section 7: Summary
|
|
530
|
+
# ---------------------------------------------------------------------------
|
|
531
|
+
print_summary() {
|
|
532
|
+
if $JSON_MODE; then
|
|
533
|
+
print_json_summary
|
|
534
|
+
return
|
|
535
|
+
fi
|
|
536
|
+
|
|
537
|
+
local total=$((GREEN_COUNT + YELLOW_COUNT + RED_COUNT))
|
|
538
|
+
local result_label result_color
|
|
539
|
+
|
|
540
|
+
if (( RED_COUNT > 0 )); then
|
|
541
|
+
result_label="NOT READY (${RED_COUNT} error(s), ${YELLOW_COUNT} warning(s))"
|
|
542
|
+
result_color="$RED"
|
|
543
|
+
elif (( YELLOW_COUNT > 0 )); then
|
|
544
|
+
result_label="READY (${YELLOW_COUNT} warning(s))"
|
|
545
|
+
result_color="$YELLOW"
|
|
546
|
+
else
|
|
547
|
+
result_label="READY"
|
|
548
|
+
result_color="$GREEN"
|
|
549
|
+
fi
|
|
550
|
+
|
|
551
|
+
# Determine box width — find the longest summary line
|
|
552
|
+
local max_len=44 # minimum width
|
|
553
|
+
for (( i=0; i<${#SUMMARY_LABELS[@]}; i++ )); do
|
|
554
|
+
local label="${SUMMARY_LABELS[$i]}"
|
|
555
|
+
# Account for symbol width (emoji + space = ~4 visible chars)
|
|
556
|
+
local line_len=$(( ${#label} + 5 ))
|
|
557
|
+
if (( line_len > max_len )); then
|
|
558
|
+
max_len=$line_len
|
|
559
|
+
fi
|
|
560
|
+
done
|
|
561
|
+
# Also check result line
|
|
562
|
+
local result_line_len=$(( ${#result_label} + 10 ))
|
|
563
|
+
if (( result_line_len > max_len )); then
|
|
564
|
+
max_len=$result_line_len
|
|
565
|
+
fi
|
|
566
|
+
|
|
567
|
+
local box_width=$((max_len + 4))
|
|
568
|
+
|
|
569
|
+
# Build horizontal rules
|
|
570
|
+
local h_double h_single
|
|
571
|
+
h_double=""
|
|
572
|
+
h_single=""
|
|
573
|
+
for (( j=0; j<box_width; j++ )); do
|
|
574
|
+
h_double+="═"
|
|
575
|
+
h_single+="═"
|
|
576
|
+
done
|
|
577
|
+
|
|
578
|
+
printf "\n"
|
|
579
|
+
printf "${BOLD}╔%s╗${RESET}\n" "$h_double"
|
|
580
|
+
printf "${BOLD}║ cf-doctor Summary%-*s║${RESET}\n" $((box_width - 20)) ""
|
|
581
|
+
printf "${BOLD}╠%s╣${RESET}\n" "$h_single"
|
|
582
|
+
|
|
583
|
+
for (( i=0; i<${#SUMMARY_LABELS[@]}; i++ )); do
|
|
584
|
+
local sym="${SUMMARY_SYMBOLS[$i]}"
|
|
585
|
+
local label="${SUMMARY_LABELS[$i]}"
|
|
586
|
+
local color="${SUMMARY_COLORS[$i]}"
|
|
587
|
+
local c=""
|
|
588
|
+
|
|
589
|
+
case "$color" in
|
|
590
|
+
GREEN) c="$GREEN" ;;
|
|
591
|
+
YELLOW) c="$YELLOW" ;;
|
|
592
|
+
RED) c="$RED" ;;
|
|
593
|
+
esac
|
|
594
|
+
|
|
595
|
+
# Calculate padding. Emoji symbols have variable display width.
|
|
596
|
+
# We use a fixed approach: print symbol + space + label, pad to box_width
|
|
597
|
+
local content="${sym} ${label}"
|
|
598
|
+
# Visible length approximation: emoji ~2 chars display + space + label
|
|
599
|
+
local visible_len=$(( ${#label} + 4 ))
|
|
600
|
+
local pad=$(( box_width - visible_len ))
|
|
601
|
+
if (( pad < 0 )); then pad=0; fi
|
|
602
|
+
|
|
603
|
+
printf "${BOLD}║${RESET} ${c}%s %s${RESET}%-*s${BOLD}║${RESET}\n" "$sym" "$label" "$pad" ""
|
|
604
|
+
done
|
|
605
|
+
|
|
606
|
+
printf "${BOLD}╠%s╣${RESET}\n" "$h_single"
|
|
607
|
+
# Result line
|
|
608
|
+
local result_vis_len=$(( ${#result_label} + 10 ))
|
|
609
|
+
local result_pad=$(( box_width - result_vis_len ))
|
|
610
|
+
if (( result_pad < 0 )); then result_pad=0; fi
|
|
611
|
+
printf "${BOLD}║${RESET} ${result_color}Result: %s${RESET}%-*s${BOLD}║${RESET}\n" "$result_label" "$result_pad" ""
|
|
612
|
+
printf "${BOLD}╚%s╝${RESET}\n" "$h_double"
|
|
613
|
+
printf "\n"
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
print_json_summary() {
|
|
617
|
+
local result
|
|
618
|
+
if (( RED_COUNT > 0 )); then
|
|
619
|
+
result="NOT_READY"
|
|
620
|
+
elif (( YELLOW_COUNT > 0 )); then
|
|
621
|
+
result="READY_WITH_WARNINGS"
|
|
622
|
+
else
|
|
623
|
+
result="READY"
|
|
624
|
+
fi
|
|
625
|
+
|
|
626
|
+
printf '{\n'
|
|
627
|
+
printf ' "result": "%s",\n' "$result"
|
|
628
|
+
printf ' "counts": {"green": %d, "yellow": %d, "red": %d},\n' \
|
|
629
|
+
"$GREEN_COUNT" "$YELLOW_COUNT" "$RED_COUNT"
|
|
630
|
+
printf ' "fix_mode": %s,\n' "$FIX_MODE"
|
|
631
|
+
printf ' "checks": {\n'
|
|
632
|
+
|
|
633
|
+
local first=true
|
|
634
|
+
for (( j=0; j<${#JSON_CHECK_KEYS[@]}; j++ )); do
|
|
635
|
+
if $first; then
|
|
636
|
+
first=false
|
|
637
|
+
else
|
|
638
|
+
printf ',\n'
|
|
639
|
+
fi
|
|
640
|
+
printf ' "%s": %s' "${JSON_CHECK_KEYS[$j]}" "${JSON_CHECK_VALS[$j]}"
|
|
641
|
+
done
|
|
642
|
+
|
|
643
|
+
printf '\n },\n'
|
|
644
|
+
printf ' "summary": [\n'
|
|
645
|
+
|
|
646
|
+
for (( i=0; i<${#SUMMARY_LABELS[@]}; i++ )); do
|
|
647
|
+
local comma=""
|
|
648
|
+
if (( i < ${#SUMMARY_LABELS[@]} - 1 )); then comma=","; fi
|
|
649
|
+
printf ' {"status": "%s", "label": %s}%s\n' \
|
|
650
|
+
"${SUMMARY_COLORS[$i]}" \
|
|
651
|
+
"$(json_escape_string "${SUMMARY_LABELS[$i]}")" \
|
|
652
|
+
"$comma"
|
|
653
|
+
done
|
|
654
|
+
|
|
655
|
+
printf ' ]\n'
|
|
656
|
+
printf '}\n'
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
# ---------------------------------------------------------------------------
|
|
660
|
+
# Main
|
|
661
|
+
# ---------------------------------------------------------------------------
|
|
662
|
+
main() {
|
|
663
|
+
if ! $JSON_MODE; then
|
|
664
|
+
printf "\n${BOLD}cf-doctor${RESET} — claude-flow + ruvector installation validator\n"
|
|
665
|
+
if $FIX_MODE; then
|
|
666
|
+
printf "${DIM}Running in --fix mode: will auto-install missing dependencies${RESET}\n"
|
|
667
|
+
fi
|
|
668
|
+
fi
|
|
669
|
+
|
|
670
|
+
check_prerequisites
|
|
671
|
+
check_installation
|
|
672
|
+
check_ruvector
|
|
673
|
+
check_directories
|
|
674
|
+
check_database
|
|
675
|
+
check_config
|
|
676
|
+
print_summary
|
|
677
|
+
|
|
678
|
+
if (( RED_COUNT > 0 )); then
|
|
679
|
+
exit 1
|
|
680
|
+
fi
|
|
681
|
+
exit 0
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
main
|