Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 44 additions & 30 deletions bin/ruflo-enable-learning
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,19 @@ while (( $# )); do
shift
done

if [[ -t 1 ]]; then
C_OK=$'\033[32m'; C_WARN=$'\033[33m'; C_FAIL=$'\033[31m'; C_DIM=$'\033[2m'; C_RESET=$'\033[0m'
else C_OK=""; C_WARN=""; C_FAIL=""; C_DIM=""; C_RESET=""; fi
ok() { printf '%s✓%s %s\n' "$C_OK" "$C_RESET" "$*"; }
warn() { printf '%s⚠%s %s\n' "$C_WARN" "$C_RESET" "$*"; }
fail() { printf '%s✗%s %s\n' "$C_FAIL" "$C_RESET" "$*"; }
dim() { printf '%s%s%s\n' "$C_DIM" "$*" "$C_RESET"; }
# Shared helpers (colors, ok/warn/fail/dim, _ruflo_need, ABI/root) from ruflo-lib.sh:
# installed copy first, then repo-relative (bin/ ↔ ../shell/).
for _cand in "$HOME/.config/ruflo/ruflo-lib.sh" "$(dirname "${BASH_SOURCE[0]:-$0}")/../shell/ruflo-lib.sh"; do
# shellcheck source=/dev/null
[ -f "$_cand" ] && { . "$_cand"; break; }
done
unset _cand
[ -n "${_RUFLO_LIB:-}" ] || { echo "ruflo-lib.sh not found — run install.sh" >&2; exit 2; }

command -v node >/dev/null 2>&1 || { fail "node not on PATH"; exit 2; }
command -v ruflo >/dev/null 2>&1 || { fail "ruflo not on PATH"; exit 2; }
_ruflo_need node ruflo
command -v ruflo-patch-native >/dev/null 2>&1 || { fail "ruflo-patch-native not on PATH (run install.sh)"; exit 2; }

NODE_ABI=$(node -e 'process.stdout.write(process.versions.modules)')
NODE_ABI=$(_ruflo_node_abi)
echo "Node ABI $NODE_ABI | ruflo $(ruflo --version 2>/dev/null | tr -d '\n')"
echo ""

Expand All @@ -60,7 +60,7 @@ fi
# On ruflo >= 3.10 the gist's controller-registry patches are already upstream:
# agentdb resolves >= 3.0 and ReasoningBank gets an embedder. We only WARN if a
# regression is detected; we do not patch a non-regressed install.
RUFLO_ROOT="$(npm root -g)/ruflo"
RUFLO_ROOT="$(_ruflo_global_root)/ruflo"
MEM="$RUFLO_ROOT/node_modules/@claude-flow/memory"
ADB_VER=$(node -e "
try{const p=require.resolve('agentdb',{paths:['$MEM']});process.stdout.write(require(p.split('/agentdb/')[0]+'/agentdb/package.json').version);}catch(e){process.stdout.write('MISSING');}" 2>/dev/null)
Expand All @@ -81,9 +81,11 @@ ruvector_repair() {
const {createRequire}=await import('node:module');
const r=createRequire('$d/package.json');
try{ r(r.resolve('$m')); process.exit(0);}catch(e){process.exit(1);}" 2>/dev/null; then
( cd "$d" && npm install "$m" --no-save --no-audit --no-fund >/dev/null 2>&1 ) \
&& ok " repaired $m in ${d#$RUFLO_ROOT/node_modules/}" \
|| warn " could not repair $m in ${d#$RUFLO_ROOT/node_modules/}"
if ( cd "$d" && npm install "$m" --no-save --no-audit --no-fund >/dev/null 2>&1 ); then
ok " repaired $m in ${d#"$RUFLO_ROOT"/node_modules/}"
else
warn " could not repair $m in ${d#"$RUFLO_ROOT"/node_modules/}"
fi
fi
done
}
Expand Down Expand Up @@ -115,24 +117,36 @@ cap() {
" 2>/dev/null
}

# Direct if/then assertions (no eval) — each runs a probe and tallies green/total.
declare -i green=0 total=0
report() { total+=1; if eval "$2"; then ok "$1"; green+=1; else fail "$1 — $3"; fi; }
pass() { total+=1; ok "$1"; green+=1; }
miss() { total+=1; fail "$1 — $2"; }

report "native better-sqlite3 (no WASM fallback)" \
'! echo "$NS" | grep -q "Using sql.js" && echo "$PN" | grep -qE "Nothing to do|already resolve native"' \
"still on sql.js/WASM — patch-native did not take"
report "HNSW vector engine (@ruvector/core → VectorDb)" \
'cap "$CLI" "@ruvector/core" "VectorDb"' \
"core present but VectorDb missing"
report "SONA engine (@ruvector/sona → SonaEngine)" \
'cap "$NEURAL" "@ruvector/sona" "SonaEngine"' \
"sona native module not loadable from @claude-flow/neural"
report "GNN layer (@ruvector/gnn → RuvectorLayer)" \
'cap "$NEURAL" "@ruvector/gnn" "RuvectorLayer"' \
"gnn native module not loadable from @claude-flow/neural"
report "ReasoningBank (agentdb v3)" \
'[[ "$ADB_VER" == 3.* ]]' \
"agentdb not v3 — ReasoningBank unavailable"
if ! echo "$NS" | grep -q "Using sql.js" && echo "$PN" | grep -qE "Nothing to do|already resolve native"; then
pass "native better-sqlite3 (no WASM fallback)"
else
miss "native better-sqlite3 (no WASM fallback)" "still on sql.js/WASM — patch-native did not take"
fi
if cap "$CLI" "@ruvector/core" "VectorDb"; then
pass "HNSW vector engine (@ruvector/core → VectorDb)"
else
miss "HNSW vector engine (@ruvector/core → VectorDb)" "core present but VectorDb missing"
fi
if cap "$NEURAL" "@ruvector/sona" "SonaEngine"; then
pass "SONA engine (@ruvector/sona → SonaEngine)"
else
miss "SONA engine (@ruvector/sona → SonaEngine)" "sona native module not loadable from @claude-flow/neural"
fi
if cap "$NEURAL" "@ruvector/gnn" "RuvectorLayer"; then
pass "GNN layer (@ruvector/gnn → RuvectorLayer)"
else
miss "GNN layer (@ruvector/gnn → RuvectorLayer)" "gnn native module not loadable from @claude-flow/neural"
fi
if [[ "$ADB_VER" == 3.* ]]; then
pass "ReasoningBank (agentdb v3)"
else
miss "ReasoningBank (agentdb v3)" "agentdb not v3 — ReasoningBank unavailable"
fi
echo ""
dim "Note: 'ruflo neural status' may still print HNSW/Training as 'Not loaded' — that is"
dim "a lazy per-process display (getHNSWStatus), not real dormancy. Prove the loop with"
Expand Down
17 changes: 10 additions & 7 deletions bin/ruflo-learning-verify
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,20 @@ while (( $# )); do
esac
shift
done
if [[ -t 1 ]]; then C_OK=$'\033[32m'; C_FAIL=$'\033[31m'; C_DIM=$'\033[2m'; C_RESET=$'\033[0m'
else C_OK=""; C_FAIL=""; C_DIM=""; C_RESET=""; fi
ok() { printf '%s✓%s %s\n' "$C_OK" "$C_RESET" "$*"; }
fail(){ printf '%s✗%s %s\n' "$C_FAIL" "$C_RESET" "$*"; }
dim() { printf '%s%s%s\n' "$C_DIM" "$*" "$C_RESET"; }
# Shared helpers (colors, ok/fail/dim, _ruflo_need) from ruflo-lib.sh:
# installed copy first, then repo-relative (bin/ ↔ ../shell/).
for _cand in "$HOME/.config/ruflo/ruflo-lib.sh" "$(dirname "${BASH_SOURCE[0]:-$0}")/../shell/ruflo-lib.sh"; do
# shellcheck source=/dev/null
[ -f "$_cand" ] && { . "$_cand"; break; }
done
unset _cand
[ -n "${_RUFLO_LIB:-}" ] || { echo "ruflo-lib.sh not found — run install.sh" >&2; exit 2; }

command -v node >/dev/null 2>&1 || { fail "node not on PATH"; exit 2; }
command -v ruflo >/dev/null 2>&1 || { fail "ruflo not on PATH"; exit 2; }
_ruflo_need node ruflo

T=$(mktemp -d)
export CLAUDE_FLOW_DB_PATH="$T/.swarm/memory.db"
# shellcheck disable=SC2329 # invoked via trap below
cleanup(){ if (( KEEP )); then echo "kept: $T"; else rm -rf "$T"; fi; }
trap cleanup EXIT

Expand Down
39 changes: 29 additions & 10 deletions bin/ruflo-parity-test
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,16 @@ for arg in "$@"; do
esac
done

# --- terminal helpers -------------------------------------------------------
if [[ -t 1 ]]; then
C_OK=$'\033[32m'; C_FAIL=$'\033[31m'; C_DIM=$'\033[2m'
C_HEAD=$'\033[1;36m'; C_RESET=$'\033[0m'
else
C_OK=""; C_FAIL=""; C_DIM=""; C_HEAD=""; C_RESET=""
fi
# Shared helpers (colors C_OK/C_FAIL/C_DIM/C_HEAD/C_RESET, _ruflo_daemon_list) from
# ruflo-lib.sh: installed copy first, then repo-relative (bin/ ↔ ../shell/).
for _cand in "$HOME/.config/ruflo/ruflo-lib.sh" "$(dirname "${BASH_SOURCE[0]:-$0}")/../shell/ruflo-lib.sh"; do
# shellcheck source=/dev/null
[ -f "$_cand" ] && { . "$_cand"; break; }
done
unset _cand
[ -n "${_RUFLO_LIB:-}" ] || { echo "ruflo-lib.sh not found — run install.sh" >&2; exit 2; }

# --- test-specific helpers (PASS/FAIL tally, headers) -----------------------
PASS=0
FAIL=0
LOG_FILE=""
Expand All @@ -80,8 +82,22 @@ check() {
fi
}

# shellcheck disable=SC2329 # invoked via trap below
cleanup_on_exit() {
local rc=$?
# Stop the daemon ruflo-setup-project started for this throwaway workspace
# (issue #3) — orphaned whether or not we keep the dir, since the test is done.
# The daemon records the RESOLVED (pwd -P) path, so match that too.
if [[ -n "${TEST_DIR:-}" ]] && command -v _ruflo_daemon_list >/dev/null 2>&1; then
local _rdir; _rdir="$(cd "$TEST_DIR" 2>/dev/null && pwd -P || echo "$TEST_DIR")"
local _dp _dws
while IFS="$(printf '\t')" read -r _dp _dws; do
[[ "$_dws" == "$_rdir" || "$_dws" == "$TEST_DIR" ]] || continue
[[ -n "$_dp" ]] && kill "$_dp" 2>/dev/null && log "Stopped test daemon pid=$_dp ($_dws)"
done <<EOF
$(_ruflo_daemon_list)
EOF
fi
if (( FAIL == 0 && KEEP == 0 )) && [[ -n "${TEST_DIR:-}" && -d "$TEST_DIR" ]]; then
rm -rf "$TEST_DIR"
log ""
Expand All @@ -91,7 +107,7 @@ cleanup_on_exit() {
log "Test dir kept at: $TEST_DIR"
[[ -n "$LOG_FILE" ]] && log "Full log: $LOG_FILE"
fi
exit $rc
exit "$rc"
}
trap cleanup_on_exit EXIT INT TERM

Expand Down Expand Up @@ -143,7 +159,7 @@ LOG_FILE="${TEST_DIR}.log"

head_line "Create test dir"
mkdir -p "$TEST_DIR"
cd "$TEST_DIR"
cd "$TEST_DIR" || { check "test dir created" "cannot cd to $TEST_DIR" "fail"; exit 2; }
check "test dir created" "$TEST_DIR" "ok"

# Capture full output to a log file alongside the test dir
Expand Down Expand Up @@ -188,9 +204,12 @@ if [[ ! -f "$SETTINGS_FILE" ]]; then
check "settings.local.json present" "missing" "fail"
else
DB_PATH=$(python3 -c "import json; print(json.load(open('$SETTINGS_FILE')).get('env', {}).get('CLAUDE_FLOW_DB_PATH', ''))" 2>/dev/null)
# The literal token Claude Code does NOT expand — matched verbatim, never expanded here.
# shellcheck disable=SC2016
UNEXPANDED_CPD='${CLAUDE_PROJECT_DIR}'
if [[ -z "$DB_PATH" ]]; then
check "CLAUDE_FLOW_DB_PATH set" "absent or unreadable" "fail"
elif [[ "$DB_PATH" == *'${CLAUDE_PROJECT_DIR}'* ]]; then
elif [[ "$DB_PATH" == *"$UNEXPANDED_CPD"* ]]; then
check "CLAUDE_FLOW_DB_PATH resolved" "literal \${CLAUDE_PROJECT_DIR} present — Claude Code does NOT expand it; writes will silently fail" "fail"
elif [[ "$DB_PATH" != /* ]]; then
check "CLAUDE_FLOW_DB_PATH absolute" "not absolute: $DB_PATH" "fail"
Expand Down
46 changes: 17 additions & 29 deletions bin/ruflo-patch-native
Original file line number Diff line number Diff line change
Expand Up @@ -52,24 +52,20 @@ while (( $# )); do
shift
done

if [[ -t 1 ]]; then
C_OK=$'\033[32m'; C_WARN=$'\033[33m'; C_FAIL=$'\033[31m'; C_DIM=$'\033[2m'; C_RESET=$'\033[0m'
else
C_OK=""; C_WARN=""; C_FAIL=""; C_DIM=""; C_RESET=""
fi
ok() { printf '%s✓%s %s\n' "$C_OK" "$C_RESET" "$*"; }
warn() { printf '%s⚠%s %s\n' "$C_WARN" "$C_RESET" "$*"; }
fail() { printf '%s✗%s %s\n' "$C_FAIL" "$C_RESET" "$*"; }
dim() { printf '%s%s%s\n' "$C_DIM" "$*" "$C_RESET"; }
# Shared helpers (colors, ok/warn/fail/dim, _ruflo_need, native bsq3 primitives)
# from ruflo-lib.sh: installed copy first, then repo-relative (bin/ ↔ ../shell/).
for _cand in "$HOME/.config/ruflo/ruflo-lib.sh" "$(dirname "${BASH_SOURCE[0]:-$0}")/../shell/ruflo-lib.sh"; do
# shellcheck source=/dev/null
[ -f "$_cand" ] && { . "$_cand"; break; }
done
unset _cand
[ -n "${_RUFLO_LIB:-}" ] || { echo "ruflo-lib.sh not found — run install.sh" >&2; exit 2; }

command -v node >/dev/null 2>&1 || { fail "node not on PATH"; exit 2; }
command -v npm >/dev/null 2>&1 || { fail "npm not on PATH"; exit 2; }
command -v ruflo >/dev/null 2>&1 || { fail "ruflo not on PATH"; exit 2; }
_ruflo_need node npm ruflo

NODE_VER=$(node --version)
NODE_ABI=$(node -e 'process.stdout.write(process.versions.modules)')
NODE_MAJOR=$(node -e 'process.stdout.write(String(process.versions.node.split(".")[0]))')
RUFLO_ROOT="$(npm root -g)/ruflo"
NODE_ABI=$(_ruflo_node_abi)
RUFLO_ROOT="$(_ruflo_global_root)/ruflo"

echo "Node $NODE_VER (ABI $NODE_ABI) | ruflo $(ruflo --version 2>/dev/null | tr -d '\n')"
echo "ruflo root: $RUFLO_ROOT"
Expand Down Expand Up @@ -107,21 +103,13 @@ if (( ${#AGENTDB_DIRS[@]} == 0 )); then
fi

needs_patch() {
# returns 0 if this agentdb dir cannot resolve a native better-sqlite3
local agentdb_dir="$1"
node -e "
try {
const p = require.resolve('better-sqlite3', { paths: ['$agentdb_dir'] });
const base = p.split('/better-sqlite3/')[0] + '/better-sqlite3';
const hasBin = require('fs').existsSync(base + '/build/Release/better_sqlite3.node');
process.exit(hasBin ? 1 : 0); // exit 1 = native present (no patch); 0 = needs patch
} catch (e) { process.exit(0); } // unresolvable = needs patch
" 2>/dev/null
# 0 if this agentdb dir cannot resolve a native better-sqlite3 (i.e. needs the patch)
! _ruflo_bsq3_is_native "$1"
}

TO_PATCH=()
for d in "${AGENTDB_DIRS[@]}"; do
rel="${d#$RUFLO_ROOT/node_modules/}"
rel="${d#"$RUFLO_ROOT"/node_modules/}"
if needs_patch "$d"; then
echo " needs patch: $rel"
TO_PATCH+=("$d")
Expand All @@ -146,7 +134,7 @@ echo "Installing better-sqlite3@$BSQ_RANGE into ${#TO_PATCH[@]} location(s)..."
PATCH_LOG=$(mktemp)
patched=0
for d in "${TO_PATCH[@]}"; do
rel="${d#$RUFLO_ROOT/node_modules/}"
rel="${d#"$RUFLO_ROOT"/node_modules/}"
if ( cd "$d" && npm install "better-sqlite3@$BSQ_RANGE" --no-save --no-audit --no-fund >>"$PATCH_LOG" 2>&1 ); then
ok "patched $rel"
patched=$((patched+1))
Expand All @@ -160,7 +148,7 @@ echo ""
echo "Verifying native binaries..."
allnative=1
for d in "${TO_PATCH[@]}"; do
rel="${d#$RUFLO_ROOT/node_modules/}"
rel="${d#"$RUFLO_ROOT"/node_modules/}"
if needs_patch "$d"; then
fail "$rel still not native"
allnative=0
Expand All @@ -179,7 +167,7 @@ echo ""
# --- Functional smoke test --------------------------------------------------
echo "Smoke test (store → retrieve via native path)..."
TMPD=$(mktemp -d)
( cd "$TMPD"
( cd "$TMPD" || exit 1
export CLAUDE_FLOW_DB_PATH="$TMPD/.swarm/memory.db"
ruflo init --minimal --force >/dev/null 2>&1
ruflo memory init >/dev/null 2>&1
Expand Down
24 changes: 15 additions & 9 deletions bin/ruflo-security-verify
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@ while (( $# )); do
esac
shift
done
if [[ -t 1 ]]; then C_OK=$'\033[32m'; C_WARN=$'\033[33m'; C_FAIL=$'\033[31m'; C_RESET=$'\033[0m'
else C_OK=""; C_WARN=""; C_FAIL=""; C_RESET=""; fi
ok() { printf '%s✓%s %s\n' "$C_OK" "$C_RESET" "$*"; }
warn(){ printf '%s⚠%s %s\n' "$C_WARN" "$C_RESET" "$*"; }
fail(){ printf '%s✗%s %s\n' "$C_FAIL" "$C_RESET" "$*"; }
# Shared helpers (colors, ok/warn/fail, _ruflo_need, global-root) from ruflo-lib.sh:
# installed copy first, then repo-relative (bin/ ↔ ../shell/).
for _cand in "$HOME/.config/ruflo/ruflo-lib.sh" "$(dirname "${BASH_SOURCE[0]:-$0}")/../shell/ruflo-lib.sh"; do
# shellcheck source=/dev/null
[ -f "$_cand" ] && { . "$_cand"; break; }
done
unset _cand
[ -n "${_RUFLO_LIB:-}" ] || { echo "ruflo-lib.sh not found — run install.sh" >&2; exit 2; }

command -v node >/dev/null 2>&1 || { fail "node not on PATH"; exit 2; }
command -v ruflo >/dev/null 2>&1 || { fail "ruflo not on PATH"; exit 2; }
RUFLO_ROOT="$(npm root -g)/ruflo"
_ruflo_need node ruflo
RUFLO_ROOT="$(_ruflo_global_root)/ruflo"
declare -i bad=0

# 1. modules load
Expand Down Expand Up @@ -71,4 +73,8 @@ if ruflo security cve --list 2>&1 | grep -qi "no cve database"; then
fi

echo ""
(( bad == 0 )) && { ok "Security surface verified."; exit 0; } || { fail "$bad security capability/ies need attention."; exit 1; }
if (( bad == 0 )); then
ok "Security surface verified."; exit 0
else
fail "$bad security capability/ies need attention."; exit 1
fi
40 changes: 40 additions & 0 deletions docs/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,43 @@ ruflo cleanup --force # remove ruflo artifacts (dry-run by default
rm -rf .swarm .claude-flow .mcp.json
ruflo-setup-project # re-create cleanly
```

## Claude Code crashes with ENOSPC / orphan `ruflo daemon` processes pile up

**Symptom.** Claude Code dies mid-session with
`the temp filesystem at /private/tmp/claude-501/<project>/<uuid>/tasks is full
(0MB free)`, and/or `ps axww | grep "daemon start"` shows many `ruflo daemon`
processes — some pointed at `--workspace` directories that no longer exist
(e.g. `/tmp/test-*` from `ruflo-parity-test` runs). See issue #3.

**Why.** `ruflo-setup-project` starts a per-workspace `ruflo daemon` (this is what
makes self-learning continuous). Before this fix, nothing stopped them, so each
throwaway/removed workspace left a daemon running forever. Separately, the
statusline footer used to spawn several `sqlite3` subprocesses on every render;
that volume of captured subprocess output is what fills Claude Code's size-limited
sandbox `tasks` tmpfs.

**Fix is built in now.** The statusline footer caches its QE metrics
(`RUFLO_QE_STATUSLINE_TTL_MS`, default 60000ms) and makes at most one `sqlite3`
call per TTL window. The daemon start is idempotent per workspace, and orphans are
reaped.

**Reap existing orphans:**

```bash
ruflo-daemon-gc # list daemons whose --workspace is gone
ruflo-daemon-gc --kill # stop exactly those (live-project daemons untouched)
```

`uninstall.sh` also reaps stale daemons; `uninstall.sh --this-project` additionally
stops the current repo's daemon.

**If you run many hooks and still hit the tmpfs limit**, point Claude Code's
subprocess tmpdir at your main filesystem (more space than the sandbox tmpfs) by
adding to `~/.claude/settings.json`:

```json
{ "env": { "CLAUDE_CODE_TMPDIR": "/Users/<you>/tmp/claude-code" } }
```

Create the directory first (`mkdir -p ~/tmp/claude-code`).
Loading