@sparkleideas/claude-flow-patch 3.1.0-alpha.44.patch.10
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/AGENTS.md +162 -0
- package/CLAUDE.md +506 -0
- package/README.md +351 -0
- package/bin/claude-flow-patch.mjs +148 -0
- package/check-patches.sh +195 -0
- package/lib/categories.json +15 -0
- package/lib/common.py +97 -0
- package/lib/discover.mjs +181 -0
- package/lib/discover.sh +160 -0
- package/package.json +86 -0
- package/patch/010-CF-001-doctor-yaml/README.md +11 -0
- package/patch/010-CF-001-doctor-yaml/fix.py +20 -0
- package/patch/010-CF-001-doctor-yaml/sentinel +1 -0
- package/patch/020-CF-002-config-export-yaml/README.md +11 -0
- package/patch/020-CF-002-config-export-yaml/fix.py +130 -0
- package/patch/020-CF-002-config-export-yaml/sentinel +1 -0
- package/patch/030-DM-001-daemon-log-zero/README.md +12 -0
- package/patch/030-DM-001-daemon-log-zero/fix.py +37 -0
- package/patch/030-DM-001-daemon-log-zero/sentinel +1 -0
- package/patch/040-DM-002-cpu-load-threshold/README.md +11 -0
- package/patch/040-DM-002-cpu-load-threshold/fix.py +6 -0
- package/patch/040-DM-002-cpu-load-threshold/sentinel +1 -0
- package/patch/050-DM-003-macos-freemem/README.md +11 -0
- package/patch/050-DM-003-macos-freemem/fix.py +7 -0
- package/patch/050-DM-003-macos-freemem/sentinel +1 -0
- package/patch/060-DM-004-preload-worker-stub/README.md +11 -0
- package/patch/060-DM-004-preload-worker-stub/fix.py +34 -0
- package/patch/060-DM-004-preload-worker-stub/sentinel +1 -0
- package/patch/070-DM-005-consolidation-worker-stub/README.md +11 -0
- package/patch/070-DM-005-consolidation-worker-stub/fix.py +46 -0
- package/patch/070-DM-005-consolidation-worker-stub/sentinel +1 -0
- package/patch/080-EM-001-embedding-ignores-config/README.md +11 -0
- package/patch/080-EM-001-embedding-ignores-config/fix.py +111 -0
- package/patch/080-EM-001-embedding-ignores-config/sentinel +1 -0
- package/patch/090-EM-002-transformers-cache-eacces/README.md +11 -0
- package/patch/090-EM-002-transformers-cache-eacces/fix.sh +12 -0
- package/patch/090-EM-002-transformers-cache-eacces/sentinel +1 -0
- package/patch/100-GV-001-hnsw-ghost-vectors/README.md +11 -0
- package/patch/100-GV-001-hnsw-ghost-vectors/fix.py +34 -0
- package/patch/100-GV-001-hnsw-ghost-vectors/sentinel +1 -0
- package/patch/110-HK-001-post-edit-file-path/README.md +44 -0
- package/patch/110-HK-001-post-edit-file-path/fix.py +23 -0
- package/patch/110-HK-001-post-edit-file-path/sentinel +1 -0
- package/patch/120-HK-002-hooks-tools-stub/README.md +36 -0
- package/patch/120-HK-002-hooks-tools-stub/fix.py +155 -0
- package/patch/120-HK-002-hooks-tools-stub/sentinel +1 -0
- package/patch/130-HK-003-metrics-hardcoded/README.md +30 -0
- package/patch/130-HK-003-metrics-hardcoded/fix.py +82 -0
- package/patch/130-HK-003-metrics-hardcoded/sentinel +1 -0
- package/patch/135-HK-004-respect-daemon-autostart/README.md +11 -0
- package/patch/135-HK-004-respect-daemon-autostart/fix.py +14 -0
- package/patch/135-HK-004-respect-daemon-autostart/sentinel +1 -0
- package/patch/137-HK-005-daemon-pid-guard/README.md +11 -0
- package/patch/137-HK-005-daemon-pid-guard/fix.py +53 -0
- package/patch/137-HK-005-daemon-pid-guard/sentinel +2 -0
- package/patch/140-HW-001-stdin-hang/README.md +11 -0
- package/patch/140-HW-001-stdin-hang/fix.py +6 -0
- package/patch/140-HW-001-stdin-hang/sentinel +1 -0
- package/patch/150-HW-002-failures-swallowed/README.md +11 -0
- package/patch/150-HW-002-failures-swallowed/fix.py +42 -0
- package/patch/150-HW-002-failures-swallowed/sentinel +1 -0
- package/patch/160-HW-003-aggressive-intervals/README.md +11 -0
- package/patch/160-HW-003-aggressive-intervals/fix.py +52 -0
- package/patch/160-HW-003-aggressive-intervals/sentinel +3 -0
- package/patch/170-IN-001-intelligence-stub/README.md +64 -0
- package/patch/170-IN-001-intelligence-stub/fix.py +63 -0
- package/patch/170-IN-001-intelligence-stub/sentinel +1 -0
- package/patch/180-MM-001-memory-persist-path/README.md +27 -0
- package/patch/180-MM-001-memory-persist-path/fix.py +54 -0
- package/patch/180-MM-001-memory-persist-path/sentinel +1 -0
- package/patch/190-NS-001-discovery-default-namespace/README.md +16 -0
- package/patch/190-NS-001-discovery-default-namespace/fix.py +68 -0
- package/patch/190-NS-001-discovery-default-namespace/sentinel +2 -0
- package/patch/200-NS-002-targeted-require-namespace/README.md +19 -0
- package/patch/200-NS-002-targeted-require-namespace/fix.py +158 -0
- package/patch/200-NS-002-targeted-require-namespace/sentinel +2 -0
- package/patch/210-NS-003-namespace-typo-pattern/README.md +15 -0
- package/patch/210-NS-003-namespace-typo-pattern/fix.py +23 -0
- package/patch/210-NS-003-namespace-typo-pattern/sentinel +1 -0
- package/patch/220-RS-001-better-sqlite3-node24/README.md +54 -0
- package/patch/220-RS-001-better-sqlite3-node24/fix.py +27 -0
- package/patch/220-RS-001-better-sqlite3-node24/rebuild.sh +31 -0
- package/patch/220-RS-001-better-sqlite3-node24/sentinel +2 -0
- package/patch/230-RV-001-force-learn-tick/README.md +31 -0
- package/patch/230-RV-001-force-learn-tick/fix.py +14 -0
- package/patch/230-RV-001-force-learn-tick/sentinel +2 -0
- package/patch/240-RV-002-trajectory-load/README.md +28 -0
- package/patch/240-RV-002-trajectory-load/fix.py +14 -0
- package/patch/240-RV-002-trajectory-load/sentinel +2 -0
- package/patch/250-RV-003-trajectory-stats-sync/README.md +31 -0
- package/patch/250-RV-003-trajectory-stats-sync/fix.py +18 -0
- package/patch/250-RV-003-trajectory-stats-sync/sentinel +2 -0
- package/patch/260-SG-001-init-settings/README.md +29 -0
- package/patch/260-SG-001-init-settings/fix.py +143 -0
- package/patch/260-SG-001-init-settings/sentinel +4 -0
- package/patch/270-SG-003-init-helpers-all-paths/README.md +60 -0
- package/patch/270-SG-003-init-helpers-all-paths/fix.py +165 -0
- package/patch/270-SG-003-init-helpers-all-paths/sentinel +3 -0
- package/patch/280-UI-001-intelligence-stats-crash/README.md +11 -0
- package/patch/280-UI-001-intelligence-stats-crash/fix.py +57 -0
- package/patch/280-UI-001-intelligence-stats-crash/sentinel +1 -0
- package/patch/290-UI-002-neural-status-not-loaded/README.md +11 -0
- package/patch/290-UI-002-neural-status-not-loaded/fix.py +19 -0
- package/patch/290-UI-002-neural-status-not-loaded/sentinel +1 -0
- package/patch/300-DM-006-log-rotation/README.md +12 -0
- package/patch/300-DM-006-log-rotation/fix.py +72 -0
- package/patch/300-DM-006-log-rotation/sentinel +2 -0
- package/patch/310-HW-004-runwithtimeout-orphan/README.md +11 -0
- package/patch/310-HW-004-runwithtimeout-orphan/fix.py +10 -0
- package/patch/310-HW-004-runwithtimeout-orphan/sentinel +1 -0
- package/patch/320-SG-004-wizard-parity/README.md +40 -0
- package/patch/320-SG-004-wizard-parity/fix.py +208 -0
- package/patch/320-SG-004-wizard-parity/sentinel +3 -0
- package/patch/330-SG-005-start-all-subcommand/README.md +32 -0
- package/patch/330-SG-005-start-all-subcommand/fix.py +58 -0
- package/patch/330-SG-005-start-all-subcommand/sentinel +1 -0
- package/patch-all.sh +199 -0
- package/repair-post-init.sh +263 -0
- package/scripts/preflight.mjs +249 -0
- package/scripts/upstream-log.mjs +257 -0
package/check-patches.sh
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# check-patches.sh — Dynamic sentinel checker
|
|
3
|
+
# Reads patch/*/sentinel files to verify patches are still applied.
|
|
4
|
+
# On session start: detects wipes, auto-reapplies, warns user.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# bash check-patches.sh [--global] [--target <dir>]
|
|
8
|
+
#
|
|
9
|
+
# If neither flag is given, --global is assumed.
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
|
|
13
|
+
# ── Parse arguments ──
|
|
14
|
+
DO_GLOBAL=0
|
|
15
|
+
TARGET_DIR=""
|
|
16
|
+
while [[ $# -gt 0 ]]; do
|
|
17
|
+
case $1 in
|
|
18
|
+
--global) DO_GLOBAL=1; shift ;;
|
|
19
|
+
--target) TARGET_DIR="${2:-}"; shift 2 ;;
|
|
20
|
+
*) shift ;; # ignore unknown (e.g. filter env handled by caller)
|
|
21
|
+
esac
|
|
22
|
+
done
|
|
23
|
+
|
|
24
|
+
if [[ $DO_GLOBAL -eq 0 && -z "$TARGET_DIR" ]]; then
|
|
25
|
+
DO_GLOBAL=1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# ── Shared discovery ──
|
|
29
|
+
. "$SCRIPT_DIR/lib/discover.sh"
|
|
30
|
+
|
|
31
|
+
# ── Collect installs ──
|
|
32
|
+
INSTALLS=()
|
|
33
|
+
|
|
34
|
+
if [[ $DO_GLOBAL -eq 1 ]]; then
|
|
35
|
+
while IFS= read -r line; do
|
|
36
|
+
[ -n "$line" ] && INSTALLS+=("$line")
|
|
37
|
+
done < <(discover_all_cf_installs)
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if [[ -n "$TARGET_DIR" && -d "$TARGET_DIR" ]]; then
|
|
41
|
+
while IFS= read -r line; do
|
|
42
|
+
[ -n "$line" ] && INSTALLS+=("$line")
|
|
43
|
+
done < <(discover_target_installs "$TARGET_DIR")
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
if [[ ${#INSTALLS[@]} -eq 0 ]]; then
|
|
47
|
+
echo "[PATCHES] WARN: Cannot find claude-flow CLI files"
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# ── Path resolver ──
|
|
52
|
+
|
|
53
|
+
resolve_path() {
|
|
54
|
+
local base="$1"
|
|
55
|
+
local rv_base="$2"
|
|
56
|
+
local rs_base="$3"
|
|
57
|
+
local pkg="$4"
|
|
58
|
+
local relpath="$5"
|
|
59
|
+
case "$pkg" in
|
|
60
|
+
ruvector) echo "$rv_base/$relpath" ;;
|
|
61
|
+
ruv-swarm) echo "$rs_base/$relpath" ;;
|
|
62
|
+
*) echo "$base/$relpath" ;;
|
|
63
|
+
esac
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# ── Check sentinels for a single install ──
|
|
67
|
+
# Returns 0 if all OK, 1 if any failed.
|
|
68
|
+
|
|
69
|
+
check_sentinels_for_install() {
|
|
70
|
+
local base="$1"
|
|
71
|
+
local rv_cli="$2"
|
|
72
|
+
local rs_root="$3"
|
|
73
|
+
|
|
74
|
+
# Derive ruvector base from cli path
|
|
75
|
+
local rv_base=""
|
|
76
|
+
if [ -n "$rv_cli" ]; then
|
|
77
|
+
rv_base="$(cd "$(dirname "$rv_cli")/.." 2>/dev/null && pwd)"
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
local all_ok=true
|
|
81
|
+
|
|
82
|
+
for sentinel_file in "$SCRIPT_DIR"/patch/*/sentinel; do
|
|
83
|
+
[ -f "$sentinel_file" ] || continue
|
|
84
|
+
|
|
85
|
+
# PATCH_INCLUDE / PATCH_EXCLUDE env vars filter by directory name regex
|
|
86
|
+
local dirname
|
|
87
|
+
dirname=$(basename "$(dirname "$sentinel_file")")
|
|
88
|
+
local matchname="${dirname#[0-9][0-9][0-9]-}" # strip NNN- prefix for pattern matching
|
|
89
|
+
if [ -n "${PATCH_INCLUDE:-}" ] && ! echo "$matchname" | grep -qE "$PATCH_INCLUDE"; then
|
|
90
|
+
continue
|
|
91
|
+
fi
|
|
92
|
+
if [ -n "${PATCH_EXCLUDE:-}" ] && echo "$matchname" | grep -qE "$PATCH_EXCLUDE"; then
|
|
93
|
+
continue
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Read package line (default: claude-flow)
|
|
97
|
+
local pkg="claude-flow"
|
|
98
|
+
local pkg_line
|
|
99
|
+
pkg_line=$(grep -m1 '^package:' "$sentinel_file" 2>/dev/null || true)
|
|
100
|
+
if [ -n "$pkg_line" ]; then
|
|
101
|
+
pkg="${pkg_line#package:}"
|
|
102
|
+
pkg="${pkg#"${pkg%%[![:space:]]*}"}" # trim leading whitespace
|
|
103
|
+
pkg="${pkg%%[[:space:]]*}" # trim trailing whitespace
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# Skip if required package not installed
|
|
107
|
+
case "$pkg" in
|
|
108
|
+
ruvector) [ -z "$rv_cli" ] && continue ;;
|
|
109
|
+
ruv-swarm) [ -z "$rs_root" ] && continue ;;
|
|
110
|
+
esac
|
|
111
|
+
|
|
112
|
+
# Process each line
|
|
113
|
+
while IFS= read -r line; do
|
|
114
|
+
line="${line#"${line%%[![:space:]]*}"}" # trim leading whitespace
|
|
115
|
+
[[ -z "$line" ]] && continue
|
|
116
|
+
[[ "$line" == package:* ]] && continue
|
|
117
|
+
|
|
118
|
+
if [[ "$line" == "none" ]]; then
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
elif [[ "$line" =~ ^absent\ \"(.+)\"\ (.+)$ ]]; then
|
|
122
|
+
local pattern="${BASH_REMATCH[1]}"
|
|
123
|
+
local filepath
|
|
124
|
+
filepath=$(resolve_path "$base" "$rv_base" "$rs_root" "$pkg" "${BASH_REMATCH[2]}")
|
|
125
|
+
if grep -q "$pattern" "$filepath" 2>/dev/null; then
|
|
126
|
+
all_ok=false
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
elif [[ "$line" =~ ^grep\ \"(.+)\"\ (.+)$ ]]; then
|
|
130
|
+
local pattern="${BASH_REMATCH[1]}"
|
|
131
|
+
local filepath
|
|
132
|
+
filepath=$(resolve_path "$base" "$rv_base" "$rs_root" "$pkg" "${BASH_REMATCH[2]}")
|
|
133
|
+
if ! grep -q "$pattern" "$filepath" 2>/dev/null; then
|
|
134
|
+
all_ok=false
|
|
135
|
+
fi
|
|
136
|
+
fi
|
|
137
|
+
done < "$sentinel_file"
|
|
138
|
+
done
|
|
139
|
+
|
|
140
|
+
$all_ok
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# ── Check all installs ──
|
|
144
|
+
|
|
145
|
+
any_failed=false
|
|
146
|
+
first_version=""
|
|
147
|
+
|
|
148
|
+
for entry in "${INSTALLS[@]}"; do
|
|
149
|
+
IFS=$'\t' read -r dist_src version rv_cli rs_root writable <<< "$entry"
|
|
150
|
+
# "-" is the placeholder for empty fields (bash IFS collapses consecutive tabs)
|
|
151
|
+
[ "$rv_cli" = "-" ] && rv_cli=""
|
|
152
|
+
[ "$rs_root" = "-" ] && rs_root=""
|
|
153
|
+
[ -z "$first_version" ] && first_version="$version"
|
|
154
|
+
|
|
155
|
+
if ! check_sentinels_for_install "$dist_src" "$rv_cli" "$rs_root"; then
|
|
156
|
+
any_failed=true
|
|
157
|
+
break # One failure is enough to trigger reapply
|
|
158
|
+
fi
|
|
159
|
+
done
|
|
160
|
+
|
|
161
|
+
VERSION="${first_version:-unknown}"
|
|
162
|
+
|
|
163
|
+
if ! $any_failed; then
|
|
164
|
+
echo "[PATCHES] OK: All patches verified (v$VERSION)"
|
|
165
|
+
exit 0
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
# ── Patches wiped — auto-reapply and warn ──
|
|
169
|
+
|
|
170
|
+
echo ""
|
|
171
|
+
echo "============================================"
|
|
172
|
+
echo " WARNING: claude-flow patches were wiped!"
|
|
173
|
+
echo " Likely cause: npx cache update (v$VERSION)"
|
|
174
|
+
echo "============================================"
|
|
175
|
+
echo ""
|
|
176
|
+
|
|
177
|
+
if [ -x "$SCRIPT_DIR/patch-all.sh" ]; then
|
|
178
|
+
REAPPLY_ARGS=()
|
|
179
|
+
if [[ $DO_GLOBAL -eq 1 ]]; then REAPPLY_ARGS+=(--global); fi
|
|
180
|
+
if [[ -n "$TARGET_DIR" ]]; then REAPPLY_ARGS+=(--target "$TARGET_DIR"); fi
|
|
181
|
+
bash "$SCRIPT_DIR/patch-all.sh" "${REAPPLY_ARGS[@]}"
|
|
182
|
+
echo ""
|
|
183
|
+
echo "[PATCHES] Auto-reapplied. Stopping existing daemons..."
|
|
184
|
+
npx @claude-flow/cli@latest daemon stop 2>/dev/null
|
|
185
|
+
# Fallback: kill by PID file if daemon stop missed an orphan (project-scoped, not global)
|
|
186
|
+
_pid=$(cat .claude-flow/daemon.pid 2>/dev/null)
|
|
187
|
+
if [ -n "$_pid" ]; then kill "$_pid" 2>/dev/null || true; rm -f .claude-flow/daemon.pid; fi
|
|
188
|
+
sleep 1
|
|
189
|
+
npx @claude-flow/cli@latest daemon start 2>/dev/null
|
|
190
|
+
echo "[PATCHES] Daemon restarted in background (PID: $(cat .claude-flow/daemon.pid 2>/dev/null || echo 'unknown'))"
|
|
191
|
+
echo ""
|
|
192
|
+
else
|
|
193
|
+
echo "[PATCHES] ERROR: patch-all.sh not found at $SCRIPT_DIR"
|
|
194
|
+
echo "[PATCHES] Run manually: bash ~/src/claude-flow-patch/patch-all.sh"
|
|
195
|
+
fi
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"HW": "Headless Worker",
|
|
3
|
+
"DM": "Daemon & Workers",
|
|
4
|
+
"CF": "Config & Doctor",
|
|
5
|
+
"EM": "Embeddings & HNSW",
|
|
6
|
+
"UI": "Display & Cosmetic",
|
|
7
|
+
"NS": "Memory Namespace",
|
|
8
|
+
"GV": "Ghost Vectors",
|
|
9
|
+
"IN": "Intelligence",
|
|
10
|
+
"SG": "Settings Generator",
|
|
11
|
+
"MM": "Memory Management",
|
|
12
|
+
"HK": "Hooks",
|
|
13
|
+
"RV": "RuVector Intelligence",
|
|
14
|
+
"RS": "ruv-swarm"
|
|
15
|
+
}
|
package/lib/common.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# common.py — shared patch infrastructure
|
|
2
|
+
# Extracted from apply-patches.sh. Provides patch()/patch_all() + path variables.
|
|
3
|
+
|
|
4
|
+
import sys, os, re
|
|
5
|
+
|
|
6
|
+
base = os.environ.get("BASE", "")
|
|
7
|
+
if not base or base == "/dev/null":
|
|
8
|
+
base = "" # No claude-flow/cli, paths will be invalid (patch() will skip gracefully)
|
|
9
|
+
services = base + "/services" if base else ""
|
|
10
|
+
commands = base + "/commands" if base else ""
|
|
11
|
+
memory = base + "/memory" if base else ""
|
|
12
|
+
|
|
13
|
+
applied = 0
|
|
14
|
+
skipped = 0
|
|
15
|
+
|
|
16
|
+
def patch(label, filepath, old, new):
|
|
17
|
+
global applied, skipped
|
|
18
|
+
if not filepath:
|
|
19
|
+
return # Skip if path is empty (package not found)
|
|
20
|
+
try:
|
|
21
|
+
with open(filepath, 'r') as f:
|
|
22
|
+
code = f.read()
|
|
23
|
+
if new in code:
|
|
24
|
+
skipped += 1
|
|
25
|
+
return
|
|
26
|
+
if old not in code:
|
|
27
|
+
print(f" WARN: {label} — pattern not found (code may have changed)")
|
|
28
|
+
return
|
|
29
|
+
code = code.replace(old, new, 1)
|
|
30
|
+
with open(filepath, 'w') as f:
|
|
31
|
+
f.write(code)
|
|
32
|
+
print(f" Applied: {label}")
|
|
33
|
+
applied += 1
|
|
34
|
+
except FileNotFoundError:
|
|
35
|
+
pass # Silently skip if file doesn't exist (package not installed)
|
|
36
|
+
except Exception as e:
|
|
37
|
+
print(f" ERROR: {label} — {e}")
|
|
38
|
+
|
|
39
|
+
def patch_all(label, filepath, old, new):
|
|
40
|
+
"""Replace ALL occurrences"""
|
|
41
|
+
global applied, skipped
|
|
42
|
+
if not filepath:
|
|
43
|
+
return # Skip if path is empty (package not found)
|
|
44
|
+
try:
|
|
45
|
+
with open(filepath, 'r') as f:
|
|
46
|
+
code = f.read()
|
|
47
|
+
if new in code and old not in code:
|
|
48
|
+
skipped += 1
|
|
49
|
+
return
|
|
50
|
+
if old not in code:
|
|
51
|
+
print(f" WARN: {label} — pattern not found")
|
|
52
|
+
return
|
|
53
|
+
code = code.replace(old, new)
|
|
54
|
+
with open(filepath, 'w') as f:
|
|
55
|
+
f.write(code)
|
|
56
|
+
print(f" Applied: {label}")
|
|
57
|
+
applied += 1
|
|
58
|
+
except FileNotFoundError:
|
|
59
|
+
pass # Silently skip if file doesn't exist (package not installed)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
print(f" ERROR: {label} — {e}")
|
|
62
|
+
|
|
63
|
+
# ── Target file paths ──
|
|
64
|
+
# These may be empty strings if base is not set (no claude-flow/cli found)
|
|
65
|
+
HWE = services + "/headless-worker-executor.js" if services else ""
|
|
66
|
+
WD = services + "/worker-daemon.js" if services else ""
|
|
67
|
+
DJ = commands + "/daemon.js" if commands else ""
|
|
68
|
+
DOC = commands + "/doctor.js" if commands else ""
|
|
69
|
+
MI = memory + "/memory-initializer.js" if memory else ""
|
|
70
|
+
|
|
71
|
+
MCP_MEMORY = base + "/mcp-tools/memory-tools.js" if base else ""
|
|
72
|
+
MCP_HOOKS = base + "/mcp-tools/hooks-tools.js" if base else ""
|
|
73
|
+
CLI_MEMORY = commands + "/memory.js" if commands else ""
|
|
74
|
+
CONF = commands + "/config.js" if commands else ""
|
|
75
|
+
HOOKS_CMD = commands + "/hooks.js" if commands else ""
|
|
76
|
+
NEURAL = commands + "/neural.js" if commands else ""
|
|
77
|
+
EMB_TOOLS = base + "/mcp-tools/embeddings-tools.js" if base else ""
|
|
78
|
+
|
|
79
|
+
# Init module
|
|
80
|
+
init = base + "/init" if base else ""
|
|
81
|
+
SETTINGS_GEN = init + "/settings-generator.js" if init else ""
|
|
82
|
+
HELPERS_GEN = init + "/helpers-generator.js" if init else ""
|
|
83
|
+
EXECUTOR = init + "/executor.js" if init else ""
|
|
84
|
+
TYPES = init + "/types.js" if init else ""
|
|
85
|
+
INIT_CMD = commands + "/init.js" if commands else ""
|
|
86
|
+
START_CMD = commands + "/start.js" if commands else ""
|
|
87
|
+
CMDS_INDEX = commands + "/index.js" if commands else ""
|
|
88
|
+
|
|
89
|
+
# Source helpers (shipped with package, copied by writeHelpers when source dir found)
|
|
90
|
+
_pkg_root = os.path.dirname(os.path.dirname(base)) if base else ""
|
|
91
|
+
SRC_HOOK_HANDLER = os.path.join(_pkg_root, ".claude", "helpers", "hook-handler.cjs") if _pkg_root else ""
|
|
92
|
+
|
|
93
|
+
# RuVector (separate package, path set by patch-all.sh)
|
|
94
|
+
ruvector_cli = os.environ.get("RUVECTOR_CLI", "")
|
|
95
|
+
|
|
96
|
+
# ruv-swarm root (separate package, path set by patch-all.sh via discover.sh)
|
|
97
|
+
ruv_swarm_root = os.environ.get("RUV_SWARM_ROOT", "")
|
package/lib/discover.mjs
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// lib/discover.mjs — Dynamic patch discovery
|
|
3
|
+
// Scans patch/*/ directories, parses README.md + fix.py headers for metadata.
|
|
4
|
+
// Single source of truth for scripts, sentinel checks, and documentation.
|
|
5
|
+
|
|
6
|
+
import { readdirSync, readFileSync, existsSync } from 'node:fs';
|
|
7
|
+
import { resolve, dirname } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const ROOT = resolve(__dirname, '..');
|
|
12
|
+
const PATCH_DIR = resolve(ROOT, 'patch');
|
|
13
|
+
|
|
14
|
+
// Category labels from lib/categories.json
|
|
15
|
+
const CATEGORY_MAP = JSON.parse(
|
|
16
|
+
readFileSync(resolve(__dirname, 'categories.json'), 'utf-8')
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Parse a patch README.md for doc metadata.
|
|
21
|
+
* Expected format:
|
|
22
|
+
* # {ID}: {title}
|
|
23
|
+
* **Severity**: {severity}
|
|
24
|
+
* **GitHub**: [{label}]({url})
|
|
25
|
+
* ## Files Patched
|
|
26
|
+
* - {file}
|
|
27
|
+
* ## Ops
|
|
28
|
+
* {N} op(s) in fix.py
|
|
29
|
+
*/
|
|
30
|
+
function parseReadme(readmePath, dirName) {
|
|
31
|
+
const text = readFileSync(readmePath, 'utf-8');
|
|
32
|
+
const lines = text.split('\n');
|
|
33
|
+
|
|
34
|
+
const titleMatch = lines[0]?.match(/^#\s+(\S+):\s+(.+)/);
|
|
35
|
+
const stripped = dirName.replace(/^\d+-/, '');
|
|
36
|
+
const id = titleMatch?.[1] ?? stripped.split('-').slice(0, 2).join('-');
|
|
37
|
+
const title = titleMatch?.[2]?.trim() ?? '';
|
|
38
|
+
|
|
39
|
+
const sevLine = lines.find(l => l.startsWith('**Severity**'));
|
|
40
|
+
const severity = sevLine?.match(/\*\*Severity\*\*:\s*(\S+)/)?.[1] ?? 'Unknown';
|
|
41
|
+
|
|
42
|
+
const ghLine = lines.find(l => l.startsWith('**GitHub**'));
|
|
43
|
+
const ghMatch = ghLine?.match(/\[([^\]]+)\]\(([^)]+)\)/);
|
|
44
|
+
const github = ghMatch?.[1] ?? '';
|
|
45
|
+
const githubUrl = ghMatch?.[2] ?? '';
|
|
46
|
+
|
|
47
|
+
const filesIdx = lines.findIndex(l => /^##\s+Files Patched/i.test(l));
|
|
48
|
+
const files = [];
|
|
49
|
+
if (filesIdx >= 0) {
|
|
50
|
+
for (let i = filesIdx + 1; i < lines.length; i++) {
|
|
51
|
+
const m = lines[i].match(/^-\s+`?([^`\n]+)`?\s*$/);
|
|
52
|
+
if (m) files.push(m[1].trim());
|
|
53
|
+
else if (lines[i].startsWith('#') || (lines[i].trim() === '' && files.length > 0)) break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const opsLine = lines.find(l => /^\d+\s+ops?\b/i.test(l));
|
|
58
|
+
const ops = opsLine ? parseInt(opsLine, 10) : 0;
|
|
59
|
+
|
|
60
|
+
return { id, title, severity, github, githubUrl, files, ops };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Parse a sentinel file for verification metadata.
|
|
65
|
+
* Each line is one of:
|
|
66
|
+
* package: <name> — target package (default: @claude-flow/cli)
|
|
67
|
+
* grep "<pattern>" <file> — pass if pattern found
|
|
68
|
+
* absent "<pattern>" <file> — pass if pattern NOT found
|
|
69
|
+
* none — skip verification
|
|
70
|
+
*/
|
|
71
|
+
function parseSentinels(sentinelPath) {
|
|
72
|
+
const text = readFileSync(sentinelPath, 'utf-8');
|
|
73
|
+
const lines = text.split('\n');
|
|
74
|
+
|
|
75
|
+
let pkg = null;
|
|
76
|
+
const sentinels = [];
|
|
77
|
+
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
const trimmed = line.trim();
|
|
80
|
+
if (!trimmed) continue;
|
|
81
|
+
|
|
82
|
+
if (trimmed.startsWith('package:')) {
|
|
83
|
+
pkg = trimmed.replace('package:', '').trim();
|
|
84
|
+
} else if (trimmed === 'none') {
|
|
85
|
+
sentinels.push({ type: 'none' });
|
|
86
|
+
} else {
|
|
87
|
+
const absentMatch = trimmed.match(/^absent\s+"(.+)"\s+(.+)$/);
|
|
88
|
+
if (absentMatch) {
|
|
89
|
+
sentinels.push({ type: 'absent', pattern: absentMatch[1], file: absentMatch[2] });
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const grepMatch = trimmed.match(/^grep\s+"(.+)"\s+(.+)$/);
|
|
93
|
+
if (grepMatch) {
|
|
94
|
+
sentinels.push({ type: 'grep', pattern: grepMatch[1], file: grepMatch[2] });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { package: pkg, sentinels };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Discover all patches. Returns structured JSON with everything needed
|
|
104
|
+
* by scripts, sentinel checks, and documentation.
|
|
105
|
+
*/
|
|
106
|
+
export function discover() {
|
|
107
|
+
let dirs;
|
|
108
|
+
try {
|
|
109
|
+
dirs = readdirSync(PATCH_DIR, { withFileTypes: true })
|
|
110
|
+
.filter(d => d.isDirectory())
|
|
111
|
+
.map(d => d.name)
|
|
112
|
+
.sort();
|
|
113
|
+
} catch {
|
|
114
|
+
return { patches: [], categories: {}, stats: { total: 0, categories: 0 } };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const patches = [];
|
|
118
|
+
const categorySet = new Set();
|
|
119
|
+
|
|
120
|
+
for (const dirName of dirs) {
|
|
121
|
+
const readmePath = resolve(PATCH_DIR, dirName, 'README.md');
|
|
122
|
+
if (!existsSync(readmePath)) continue;
|
|
123
|
+
|
|
124
|
+
const meta = parseReadme(readmePath, dirName);
|
|
125
|
+
const prefix = meta.id.split('-')[0];
|
|
126
|
+
const stripped = dirName.replace(/^\d+-/, '');
|
|
127
|
+
const slug = stripped.replace(/^[A-Z]+-\d+-/, '');
|
|
128
|
+
const orderMatch = dirName.match(/^(\d+)-/);
|
|
129
|
+
const order = orderMatch ? parseInt(orderMatch[1], 10) : null;
|
|
130
|
+
const hasPy = existsSync(resolve(PATCH_DIR, dirName, 'fix.py'));
|
|
131
|
+
const hasSh = existsSync(resolve(PATCH_DIR, dirName, 'fix.sh'));
|
|
132
|
+
const fixType = hasPy ? 'python' : hasSh ? 'shell' : 'unknown';
|
|
133
|
+
const category = CATEGORY_MAP[prefix] ?? prefix;
|
|
134
|
+
categorySet.add(category);
|
|
135
|
+
|
|
136
|
+
// Parse sentinel metadata from dedicated sentinel file
|
|
137
|
+
const sentinelPath = resolve(PATCH_DIR, dirName, 'sentinel');
|
|
138
|
+
const sentinel = existsSync(sentinelPath) ? parseSentinels(sentinelPath) : { package: null, sentinels: [] };
|
|
139
|
+
|
|
140
|
+
patches.push({
|
|
141
|
+
id: meta.id,
|
|
142
|
+
order,
|
|
143
|
+
slug,
|
|
144
|
+
dir: dirName,
|
|
145
|
+
title: meta.title,
|
|
146
|
+
severity: meta.severity,
|
|
147
|
+
github: meta.github,
|
|
148
|
+
githubUrl: meta.githubUrl,
|
|
149
|
+
category,
|
|
150
|
+
prefix,
|
|
151
|
+
type: fixType,
|
|
152
|
+
files: meta.files,
|
|
153
|
+
ops: meta.ops,
|
|
154
|
+
package: sentinel.package,
|
|
155
|
+
sentinels: sentinel.sentinels,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Build categories object: { prefix: label } for active categories
|
|
160
|
+
const categories = {};
|
|
161
|
+
for (const p of patches) {
|
|
162
|
+
if (!categories[p.prefix]) {
|
|
163
|
+
categories[p.prefix] = CATEGORY_MAP[p.prefix] ?? p.prefix;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
patches,
|
|
169
|
+
categories,
|
|
170
|
+
stats: {
|
|
171
|
+
total: patches.length,
|
|
172
|
+
categories: categorySet.size,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// CLI: `node lib/discover.mjs` prints JSON to stdout
|
|
178
|
+
const thisFile = resolve(__dirname, 'discover.mjs');
|
|
179
|
+
if (process.argv[1] && resolve(process.argv[1]) === thisFile) {
|
|
180
|
+
console.log(JSON.stringify(discover(), null, 2));
|
|
181
|
+
}
|
package/lib/discover.sh
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# lib/discover.sh — Shared install discovery for patch-all.sh, check-patches.sh, repair-post-init.sh
|
|
3
|
+
#
|
|
4
|
+
# Finds ALL @claude-flow/cli installations across three patterns:
|
|
5
|
+
# 1. Direct npx: {npx_cache}/{hash}/node_modules/@claude-flow/cli/dist/src/
|
|
6
|
+
# 2. Umbrella npx: {npx_cache}/{hash}/node_modules/claude-flow/v3/@claude-flow/cli/dist/src/
|
|
7
|
+
# 3. Umbrella global: {npm_prefix}/lib/node_modules/claude-flow/v3/@claude-flow/cli/dist/src/
|
|
8
|
+
#
|
|
9
|
+
# Output: tab-separated lines:
|
|
10
|
+
# dist_src \t version \t ruvector_cli \t ruv_swarm_root \t writable(yes|no)
|
|
11
|
+
|
|
12
|
+
# ── npx cache roots ──
|
|
13
|
+
|
|
14
|
+
_cfp_npx_cache_roots() {
|
|
15
|
+
# Linux / macOS
|
|
16
|
+
local home_npx="$HOME/.npm/_npx"
|
|
17
|
+
[ -d "$home_npx" ] && echo "$home_npx"
|
|
18
|
+
|
|
19
|
+
# Windows (Git Bash / MSYS2): $LOCALAPPDATA/npm-cache/_npx
|
|
20
|
+
if [ -n "${LOCALAPPDATA:-}" ]; then
|
|
21
|
+
local win_npx
|
|
22
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
23
|
+
win_npx="$(cygpath "$LOCALAPPDATA")/npm-cache/_npx"
|
|
24
|
+
else
|
|
25
|
+
win_npx="$LOCALAPPDATA/npm-cache/_npx"
|
|
26
|
+
fi
|
|
27
|
+
[ -d "$win_npx" ] && echo "$win_npx"
|
|
28
|
+
fi
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# ── Probe a single node_modules directory ──
|
|
32
|
+
# Args: <node_modules_dir>
|
|
33
|
+
# Outputs tab-separated lines for each valid install found.
|
|
34
|
+
|
|
35
|
+
_cfp_probe_node_modules() {
|
|
36
|
+
local nm_dir="$1"
|
|
37
|
+
[ -d "$nm_dir" ] || return 0
|
|
38
|
+
|
|
39
|
+
local layouts=(
|
|
40
|
+
"@claude-flow/cli/dist/src"
|
|
41
|
+
"claude-flow/v3/@claude-flow/cli/dist/src"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
local _seen_real=()
|
|
45
|
+
|
|
46
|
+
for layout in "${layouts[@]}"; do
|
|
47
|
+
local dist_src="$nm_dir/$layout"
|
|
48
|
+
[ -f "$dist_src/memory/memory-initializer.js" ] || continue
|
|
49
|
+
|
|
50
|
+
# Deduplicate by realpath
|
|
51
|
+
local real
|
|
52
|
+
real="$(realpath "$dist_src" 2>/dev/null || echo "$dist_src")"
|
|
53
|
+
local dup=0
|
|
54
|
+
for s in "${_seen_real[@]}"; do
|
|
55
|
+
[ "$s" = "$real" ] && { dup=1; break; }
|
|
56
|
+
done
|
|
57
|
+
[ "$dup" -eq 1 ] && continue
|
|
58
|
+
_seen_real+=("$real")
|
|
59
|
+
|
|
60
|
+
# Version
|
|
61
|
+
local pkg_json="$dist_src/../../package.json"
|
|
62
|
+
local version
|
|
63
|
+
version=$(grep -o '"version": *"[^"]*"' "$pkg_json" 2>/dev/null | head -1 | cut -d'"' -f4)
|
|
64
|
+
[ -z "$version" ] && version="unknown"
|
|
65
|
+
|
|
66
|
+
# Companion: ruvector — search sibling node_modules and umbrella's v3/node_modules
|
|
67
|
+
local rv_cli="-"
|
|
68
|
+
local search_dirs=("$nm_dir")
|
|
69
|
+
# If umbrella layout, also search inside v3/node_modules
|
|
70
|
+
if [[ "$layout" == claude-flow/* ]]; then
|
|
71
|
+
local v3_nm="$nm_dir/claude-flow/v3/node_modules"
|
|
72
|
+
[ -d "$v3_nm" ] && search_dirs+=("$v3_nm")
|
|
73
|
+
fi
|
|
74
|
+
for sd in "${search_dirs[@]}"; do
|
|
75
|
+
if [ -f "$sd/ruvector/bin/cli.js" ]; then
|
|
76
|
+
rv_cli="$(cd "$sd/ruvector/bin" 2>/dev/null && pwd)/cli.js"
|
|
77
|
+
break
|
|
78
|
+
fi
|
|
79
|
+
done
|
|
80
|
+
|
|
81
|
+
# Companion: ruv-swarm root
|
|
82
|
+
local rs_root="-"
|
|
83
|
+
for sd in "${search_dirs[@]}"; do
|
|
84
|
+
if [ -f "$sd/ruv-swarm/package.json" ]; then
|
|
85
|
+
rs_root="$(cd "$sd/ruv-swarm" 2>/dev/null && pwd)"
|
|
86
|
+
break
|
|
87
|
+
fi
|
|
88
|
+
done
|
|
89
|
+
|
|
90
|
+
# Writable check
|
|
91
|
+
local writable="yes"
|
|
92
|
+
[ -w "$dist_src/memory/memory-initializer.js" ] || writable="no"
|
|
93
|
+
|
|
94
|
+
# Use "-" for empty fields to prevent bash IFS collapsing consecutive delimiters
|
|
95
|
+
printf '%s\t%s\t%s\t%s\t%s\n' "$dist_src" "$version" "$rv_cli" "$rs_root" "$writable"
|
|
96
|
+
done
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# ── Discover all global installs ──
|
|
100
|
+
# Outputs tab-separated lines (same format as _cfp_probe_node_modules).
|
|
101
|
+
|
|
102
|
+
discover_all_cf_installs() {
|
|
103
|
+
local _global_seen=()
|
|
104
|
+
|
|
105
|
+
# 1. npx cache directories
|
|
106
|
+
while IFS= read -r cache_root; do
|
|
107
|
+
[ -n "$cache_root" ] || continue
|
|
108
|
+
for hash_dir in "$cache_root"/*/; do
|
|
109
|
+
[ -d "$hash_dir/node_modules" ] || continue
|
|
110
|
+
while IFS= read -r line; do
|
|
111
|
+
[ -n "$line" ] || continue
|
|
112
|
+
local ds="${line%% *}"
|
|
113
|
+
local real
|
|
114
|
+
real="$(realpath "$ds" 2>/dev/null || echo "$ds")"
|
|
115
|
+
local dup=0
|
|
116
|
+
for s in "${_global_seen[@]}"; do
|
|
117
|
+
[ "$s" = "$real" ] && { dup=1; break; }
|
|
118
|
+
done
|
|
119
|
+
[ "$dup" -eq 1 ] && continue
|
|
120
|
+
_global_seen+=("$real")
|
|
121
|
+
echo "$line"
|
|
122
|
+
done < <(_cfp_probe_node_modules "$hash_dir/node_modules")
|
|
123
|
+
done
|
|
124
|
+
done < <(_cfp_npx_cache_roots)
|
|
125
|
+
|
|
126
|
+
# 2. Global npm prefix
|
|
127
|
+
local npm_prefix
|
|
128
|
+
npm_prefix="$(npm config get prefix 2>/dev/null)" || true
|
|
129
|
+
if [ -n "$npm_prefix" ]; then
|
|
130
|
+
# Linux/macOS: {prefix}/lib/node_modules
|
|
131
|
+
# Windows: {prefix}/node_modules (no /lib/)
|
|
132
|
+
local prefix_dirs=("$npm_prefix/lib/node_modules" "$npm_prefix/node_modules")
|
|
133
|
+
for pdir in "${prefix_dirs[@]}"; do
|
|
134
|
+
[ -d "$pdir" ] || continue
|
|
135
|
+
while IFS= read -r line; do
|
|
136
|
+
[ -n "$line" ] || continue
|
|
137
|
+
local ds="${line%% *}"
|
|
138
|
+
local real
|
|
139
|
+
real="$(realpath "$ds" 2>/dev/null || echo "$ds")"
|
|
140
|
+
local dup=0
|
|
141
|
+
for s in "${_global_seen[@]}"; do
|
|
142
|
+
[ "$s" = "$real" ] && { dup=1; break; }
|
|
143
|
+
done
|
|
144
|
+
[ "$dup" -eq 1 ] && continue
|
|
145
|
+
_global_seen+=("$real")
|
|
146
|
+
echo "$line"
|
|
147
|
+
done < <(_cfp_probe_node_modules "$pdir")
|
|
148
|
+
done
|
|
149
|
+
fi
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
# ── Discover installs in a --target directory ──
|
|
153
|
+
# Args: <target_dir>
|
|
154
|
+
# Now handles both direct and umbrella layouts.
|
|
155
|
+
|
|
156
|
+
discover_target_installs() {
|
|
157
|
+
local dir="$1"
|
|
158
|
+
[ -d "$dir/node_modules" ] || return 0
|
|
159
|
+
_cfp_probe_node_modules "$dir/node_modules"
|
|
160
|
+
}
|