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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -0
  3. package/bin/cf-doctor.js +18 -0
  4. package/dist/doctor.d.ts +21 -0
  5. package/dist/doctor.d.ts.map +1 -0
  6. package/dist/doctor.js +33 -0
  7. package/dist/doctor.js.map +1 -0
  8. package/dist/index.d.ts +9 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +9 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/mcp-server.d.ts +12 -0
  13. package/dist/mcp-server.d.ts.map +1 -0
  14. package/dist/mcp-server.js +200 -0
  15. package/dist/mcp-server.js.map +1 -0
  16. package/dist/patches/apply.d.ts +7 -0
  17. package/dist/patches/apply.d.ts.map +1 -0
  18. package/dist/patches/apply.js +51 -0
  19. package/dist/patches/apply.js.map +1 -0
  20. package/dist/persistence/episodes.d.ts +42 -0
  21. package/dist/persistence/episodes.d.ts.map +1 -0
  22. package/dist/persistence/episodes.js +160 -0
  23. package/dist/persistence/episodes.js.map +1 -0
  24. package/dist/persistence/index.d.ts +4 -0
  25. package/dist/persistence/index.d.ts.map +1 -0
  26. package/dist/persistence/index.js +4 -0
  27. package/dist/persistence/index.js.map +1 -0
  28. package/dist/persistence/q-table.d.ts +42 -0
  29. package/dist/persistence/q-table.d.ts.map +1 -0
  30. package/dist/persistence/q-table.js +138 -0
  31. package/dist/persistence/q-table.js.map +1 -0
  32. package/dist/persistence/sona.d.ts +45 -0
  33. package/dist/persistence/sona.d.ts.map +1 -0
  34. package/dist/persistence/sona.js +142 -0
  35. package/dist/persistence/sona.js.map +1 -0
  36. package/package.json +63 -0
  37. package/patches/README.md +68 -0
  38. package/patches/neural-index.patch +8 -0
  39. package/patches/quick-test.patch +25 -0
  40. package/patches/sona-integration.patch +76 -0
  41. package/patches/version-bridge.patch +30 -0
  42. package/scripts/cf-doctor.sh +684 -0
  43. package/tests/run-all-tests.sh +32 -0
  44. package/tests/test-01-doctor-passes.sh +43 -0
  45. package/tests/test-02-mcp-init.sh +36 -0
  46. package/tests/test-03-agent-spawn-no-ruvector.sh +84 -0
  47. package/tests/test-04-agent-spawn-with-ruvector.sh +37 -0
  48. package/tests/test-05-learning-persists.sh +94 -0
  49. package/tests/test-06-hooks-version-bridge.sh +82 -0
  50. 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