@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.
Files changed (120) hide show
  1. package/AGENTS.md +162 -0
  2. package/CLAUDE.md +506 -0
  3. package/README.md +351 -0
  4. package/bin/claude-flow-patch.mjs +148 -0
  5. package/check-patches.sh +195 -0
  6. package/lib/categories.json +15 -0
  7. package/lib/common.py +97 -0
  8. package/lib/discover.mjs +181 -0
  9. package/lib/discover.sh +160 -0
  10. package/package.json +86 -0
  11. package/patch/010-CF-001-doctor-yaml/README.md +11 -0
  12. package/patch/010-CF-001-doctor-yaml/fix.py +20 -0
  13. package/patch/010-CF-001-doctor-yaml/sentinel +1 -0
  14. package/patch/020-CF-002-config-export-yaml/README.md +11 -0
  15. package/patch/020-CF-002-config-export-yaml/fix.py +130 -0
  16. package/patch/020-CF-002-config-export-yaml/sentinel +1 -0
  17. package/patch/030-DM-001-daemon-log-zero/README.md +12 -0
  18. package/patch/030-DM-001-daemon-log-zero/fix.py +37 -0
  19. package/patch/030-DM-001-daemon-log-zero/sentinel +1 -0
  20. package/patch/040-DM-002-cpu-load-threshold/README.md +11 -0
  21. package/patch/040-DM-002-cpu-load-threshold/fix.py +6 -0
  22. package/patch/040-DM-002-cpu-load-threshold/sentinel +1 -0
  23. package/patch/050-DM-003-macos-freemem/README.md +11 -0
  24. package/patch/050-DM-003-macos-freemem/fix.py +7 -0
  25. package/patch/050-DM-003-macos-freemem/sentinel +1 -0
  26. package/patch/060-DM-004-preload-worker-stub/README.md +11 -0
  27. package/patch/060-DM-004-preload-worker-stub/fix.py +34 -0
  28. package/patch/060-DM-004-preload-worker-stub/sentinel +1 -0
  29. package/patch/070-DM-005-consolidation-worker-stub/README.md +11 -0
  30. package/patch/070-DM-005-consolidation-worker-stub/fix.py +46 -0
  31. package/patch/070-DM-005-consolidation-worker-stub/sentinel +1 -0
  32. package/patch/080-EM-001-embedding-ignores-config/README.md +11 -0
  33. package/patch/080-EM-001-embedding-ignores-config/fix.py +111 -0
  34. package/patch/080-EM-001-embedding-ignores-config/sentinel +1 -0
  35. package/patch/090-EM-002-transformers-cache-eacces/README.md +11 -0
  36. package/patch/090-EM-002-transformers-cache-eacces/fix.sh +12 -0
  37. package/patch/090-EM-002-transformers-cache-eacces/sentinel +1 -0
  38. package/patch/100-GV-001-hnsw-ghost-vectors/README.md +11 -0
  39. package/patch/100-GV-001-hnsw-ghost-vectors/fix.py +34 -0
  40. package/patch/100-GV-001-hnsw-ghost-vectors/sentinel +1 -0
  41. package/patch/110-HK-001-post-edit-file-path/README.md +44 -0
  42. package/patch/110-HK-001-post-edit-file-path/fix.py +23 -0
  43. package/patch/110-HK-001-post-edit-file-path/sentinel +1 -0
  44. package/patch/120-HK-002-hooks-tools-stub/README.md +36 -0
  45. package/patch/120-HK-002-hooks-tools-stub/fix.py +155 -0
  46. package/patch/120-HK-002-hooks-tools-stub/sentinel +1 -0
  47. package/patch/130-HK-003-metrics-hardcoded/README.md +30 -0
  48. package/patch/130-HK-003-metrics-hardcoded/fix.py +82 -0
  49. package/patch/130-HK-003-metrics-hardcoded/sentinel +1 -0
  50. package/patch/135-HK-004-respect-daemon-autostart/README.md +11 -0
  51. package/patch/135-HK-004-respect-daemon-autostart/fix.py +14 -0
  52. package/patch/135-HK-004-respect-daemon-autostart/sentinel +1 -0
  53. package/patch/137-HK-005-daemon-pid-guard/README.md +11 -0
  54. package/patch/137-HK-005-daemon-pid-guard/fix.py +53 -0
  55. package/patch/137-HK-005-daemon-pid-guard/sentinel +2 -0
  56. package/patch/140-HW-001-stdin-hang/README.md +11 -0
  57. package/patch/140-HW-001-stdin-hang/fix.py +6 -0
  58. package/patch/140-HW-001-stdin-hang/sentinel +1 -0
  59. package/patch/150-HW-002-failures-swallowed/README.md +11 -0
  60. package/patch/150-HW-002-failures-swallowed/fix.py +42 -0
  61. package/patch/150-HW-002-failures-swallowed/sentinel +1 -0
  62. package/patch/160-HW-003-aggressive-intervals/README.md +11 -0
  63. package/patch/160-HW-003-aggressive-intervals/fix.py +52 -0
  64. package/patch/160-HW-003-aggressive-intervals/sentinel +3 -0
  65. package/patch/170-IN-001-intelligence-stub/README.md +64 -0
  66. package/patch/170-IN-001-intelligence-stub/fix.py +63 -0
  67. package/patch/170-IN-001-intelligence-stub/sentinel +1 -0
  68. package/patch/180-MM-001-memory-persist-path/README.md +27 -0
  69. package/patch/180-MM-001-memory-persist-path/fix.py +54 -0
  70. package/patch/180-MM-001-memory-persist-path/sentinel +1 -0
  71. package/patch/190-NS-001-discovery-default-namespace/README.md +16 -0
  72. package/patch/190-NS-001-discovery-default-namespace/fix.py +68 -0
  73. package/patch/190-NS-001-discovery-default-namespace/sentinel +2 -0
  74. package/patch/200-NS-002-targeted-require-namespace/README.md +19 -0
  75. package/patch/200-NS-002-targeted-require-namespace/fix.py +158 -0
  76. package/patch/200-NS-002-targeted-require-namespace/sentinel +2 -0
  77. package/patch/210-NS-003-namespace-typo-pattern/README.md +15 -0
  78. package/patch/210-NS-003-namespace-typo-pattern/fix.py +23 -0
  79. package/patch/210-NS-003-namespace-typo-pattern/sentinel +1 -0
  80. package/patch/220-RS-001-better-sqlite3-node24/README.md +54 -0
  81. package/patch/220-RS-001-better-sqlite3-node24/fix.py +27 -0
  82. package/patch/220-RS-001-better-sqlite3-node24/rebuild.sh +31 -0
  83. package/patch/220-RS-001-better-sqlite3-node24/sentinel +2 -0
  84. package/patch/230-RV-001-force-learn-tick/README.md +31 -0
  85. package/patch/230-RV-001-force-learn-tick/fix.py +14 -0
  86. package/patch/230-RV-001-force-learn-tick/sentinel +2 -0
  87. package/patch/240-RV-002-trajectory-load/README.md +28 -0
  88. package/patch/240-RV-002-trajectory-load/fix.py +14 -0
  89. package/patch/240-RV-002-trajectory-load/sentinel +2 -0
  90. package/patch/250-RV-003-trajectory-stats-sync/README.md +31 -0
  91. package/patch/250-RV-003-trajectory-stats-sync/fix.py +18 -0
  92. package/patch/250-RV-003-trajectory-stats-sync/sentinel +2 -0
  93. package/patch/260-SG-001-init-settings/README.md +29 -0
  94. package/patch/260-SG-001-init-settings/fix.py +143 -0
  95. package/patch/260-SG-001-init-settings/sentinel +4 -0
  96. package/patch/270-SG-003-init-helpers-all-paths/README.md +60 -0
  97. package/patch/270-SG-003-init-helpers-all-paths/fix.py +165 -0
  98. package/patch/270-SG-003-init-helpers-all-paths/sentinel +3 -0
  99. package/patch/280-UI-001-intelligence-stats-crash/README.md +11 -0
  100. package/patch/280-UI-001-intelligence-stats-crash/fix.py +57 -0
  101. package/patch/280-UI-001-intelligence-stats-crash/sentinel +1 -0
  102. package/patch/290-UI-002-neural-status-not-loaded/README.md +11 -0
  103. package/patch/290-UI-002-neural-status-not-loaded/fix.py +19 -0
  104. package/patch/290-UI-002-neural-status-not-loaded/sentinel +1 -0
  105. package/patch/300-DM-006-log-rotation/README.md +12 -0
  106. package/patch/300-DM-006-log-rotation/fix.py +72 -0
  107. package/patch/300-DM-006-log-rotation/sentinel +2 -0
  108. package/patch/310-HW-004-runwithtimeout-orphan/README.md +11 -0
  109. package/patch/310-HW-004-runwithtimeout-orphan/fix.py +10 -0
  110. package/patch/310-HW-004-runwithtimeout-orphan/sentinel +1 -0
  111. package/patch/320-SG-004-wizard-parity/README.md +40 -0
  112. package/patch/320-SG-004-wizard-parity/fix.py +208 -0
  113. package/patch/320-SG-004-wizard-parity/sentinel +3 -0
  114. package/patch/330-SG-005-start-all-subcommand/README.md +32 -0
  115. package/patch/330-SG-005-start-all-subcommand/fix.py +58 -0
  116. package/patch/330-SG-005-start-all-subcommand/sentinel +1 -0
  117. package/patch-all.sh +199 -0
  118. package/repair-post-init.sh +263 -0
  119. package/scripts/preflight.mjs +249 -0
  120. package/scripts/upstream-log.mjs +257 -0
package/patch-all.sh ADDED
@@ -0,0 +1,199 @@
1
+ #!/bin/bash
2
+ # patch-all.sh — Orchestrator for folder-per-defect patches
3
+ # Safe to run multiple times. Each fix.py is idempotent via patch()/patch_all().
4
+ #
5
+ # Usage:
6
+ # bash patch-all.sh [--global] [--target <dir>]
7
+ #
8
+ # Options:
9
+ # --global Patch all global installs (npx cache + npm global)
10
+ # --target <dir> Patch node_modules inside <dir>
11
+ #
12
+ # If neither flag is given, --global is assumed.
13
+
14
+ set -euo pipefail
15
+
16
+ # Parse arguments
17
+ DO_GLOBAL=0
18
+ TARGET_DIR=""
19
+ while [[ $# -gt 0 ]]; do
20
+ case $1 in
21
+ --global)
22
+ DO_GLOBAL=1
23
+ shift
24
+ ;;
25
+ --target)
26
+ TARGET_DIR="${2:-}"
27
+ if [[ -z "$TARGET_DIR" ]]; then
28
+ echo "Error: --target requires a directory argument"
29
+ exit 1
30
+ fi
31
+ shift 2
32
+ ;;
33
+ -h|--help)
34
+ echo "Usage: patch-all.sh [--global] [--target <dir>]"
35
+ echo ""
36
+ echo "Options:"
37
+ echo " --global Patch all global installs (npx cache + npm global)"
38
+ echo " --target <dir> Patch node_modules inside <dir>"
39
+ echo ""
40
+ echo "If neither flag is given, --global is assumed."
41
+ exit 0
42
+ ;;
43
+ *)
44
+ echo "Unknown option: $1"
45
+ exit 1
46
+ ;;
47
+ esac
48
+ done
49
+
50
+ # Default: --global when nothing specified
51
+ if [[ $DO_GLOBAL -eq 0 && -z "$TARGET_DIR" ]]; then
52
+ DO_GLOBAL=1
53
+ fi
54
+
55
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
56
+
57
+ # ── Shared discovery ──
58
+ . "$SCRIPT_DIR/lib/discover.sh"
59
+
60
+ # ── Collect installs ──
61
+ # Each entry: "SCOPE\tdist_src\tversion\truvector_cli\truv_swarm_root\twritable"
62
+
63
+ INSTALLS=()
64
+
65
+ if [[ $DO_GLOBAL -eq 1 ]]; then
66
+ while IFS= read -r line; do
67
+ [ -n "$line" ] && INSTALLS+=("GLOBAL $line")
68
+ done < <(discover_all_cf_installs)
69
+ fi
70
+
71
+ if [[ -n "$TARGET_DIR" ]]; then
72
+ if [[ ! -d "$TARGET_DIR" ]]; then
73
+ echo "Error: target directory does not exist: $TARGET_DIR"
74
+ exit 1
75
+ fi
76
+ TARGET_DIR="$(cd "$TARGET_DIR" && pwd)"
77
+ while IFS= read -r line; do
78
+ [ -n "$line" ] && INSTALLS+=("TARGET $line")
79
+ done < <(discover_target_installs "$TARGET_DIR")
80
+ fi
81
+
82
+ # ── Report what we found ──
83
+
84
+ TARGETS=()
85
+ if [[ $DO_GLOBAL -eq 1 ]]; then TARGETS+=(global); fi
86
+ if [[ -n "$TARGET_DIR" ]]; then TARGETS+=("$TARGET_DIR"); fi
87
+ echo "[PATCHES] Targets: ${TARGETS[*]}"
88
+ echo ""
89
+
90
+ if [[ ${#INSTALLS[@]} -eq 0 ]]; then
91
+ if [[ $DO_GLOBAL -eq 1 ]]; then
92
+ echo " Global @claude-flow/cli: not found"
93
+ echo " Global ruvector: not found"
94
+ fi
95
+ if [[ -n "$TARGET_DIR" ]]; then
96
+ echo " Target @claude-flow/cli: not found in $TARGET_DIR"
97
+ echo " Target ruvector: not found in $TARGET_DIR"
98
+ fi
99
+ echo ""
100
+ echo "[PATCHES] Complete"
101
+ exit 0
102
+ fi
103
+
104
+ for entry in "${INSTALLS[@]}"; do
105
+ IFS=$'\t' read -r scope dist_src version rv_cli rs_root writable <<< "$entry"
106
+ # "-" is the placeholder for empty fields (bash IFS collapses consecutive tabs)
107
+ [ "$rv_cli" = "-" ] && rv_cli=""
108
+ [ "$rs_root" = "-" ] && rs_root=""
109
+ echo " [$scope] @claude-flow/cli v$version at $dist_src"
110
+ [ -n "$rv_cli" ] && echo " [$scope] ruvector: $rv_cli"
111
+ [ -n "$rs_root" ] && echo " [$scope] ruv-swarm: $rs_root"
112
+ if [ "$writable" = "no" ]; then
113
+ echo " [$scope] WARNING: not writable (re-run with sudo)"
114
+ fi
115
+ done
116
+
117
+ echo ""
118
+
119
+ # ── Apply patches function ──
120
+
121
+ apply_patches() {
122
+ local base="$1"
123
+ local ruvector_cli="$2"
124
+ local ruv_swarm_root="$3"
125
+ local label="$4"
126
+
127
+ if [ -z "$base" ] && [ -z "$ruvector_cli" ]; then
128
+ echo "[$label] No packages found, skipping"
129
+ return
130
+ fi
131
+
132
+ if [ -n "$base" ]; then
133
+ echo "[$label] Patching @claude-flow/cli at: $base"
134
+ fi
135
+ if [ -n "$ruvector_cli" ]; then
136
+ echo "[$label] Patching ruvector at: $ruvector_cli"
137
+ fi
138
+
139
+ export BASE="${base:-/dev/null}"
140
+ export RUVECTOR_CLI="$ruvector_cli"
141
+ export RUV_SWARM_ROOT="$ruv_swarm_root"
142
+
143
+ # Dynamic discovery: concatenate common.py + all fix.py files sorted alphabetically.
144
+ # Alphabetical order preserves dependencies (e.g. NS-001 < NS-002 < NS-003).
145
+ #
146
+ # PATCH_INCLUDE / PATCH_EXCLUDE env vars filter by directory name regex.
147
+ python3 <(
148
+ cat "$SCRIPT_DIR/lib/common.py"
149
+
150
+ for fix in "$SCRIPT_DIR"/patch/*/fix.py; do
151
+ [ -f "$fix" ] || continue
152
+ dirname=$(basename "$(dirname "$fix")")
153
+ matchname="${dirname#[0-9][0-9][0-9]-}" # strip NNN- prefix for pattern matching
154
+ if [ -n "${PATCH_INCLUDE:-}" ] && ! echo "$matchname" | grep -qE "$PATCH_INCLUDE"; then
155
+ continue
156
+ fi
157
+ if [ -n "${PATCH_EXCLUDE:-}" ] && echo "$matchname" | grep -qE "$PATCH_EXCLUDE"; then
158
+ continue
159
+ fi
160
+ cat "$fix"
161
+ done
162
+
163
+ echo "print(f\"[$label] Done: {applied} applied, {skipped} already present\")"
164
+ )
165
+
166
+ # Shell-based patches (e.g. EM-002: transformers cache permissions)
167
+ for fix in "$SCRIPT_DIR"/patch/*/fix.sh; do
168
+ [ -f "$fix" ] || continue
169
+ dirname=$(basename "$(dirname "$fix")")
170
+ matchname="${dirname#[0-9][0-9][0-9]-}" # strip NNN- prefix for pattern matching
171
+ if [ -n "${PATCH_INCLUDE:-}" ] && ! echo "$matchname" | grep -qE "$PATCH_INCLUDE"; then
172
+ continue
173
+ fi
174
+ if [ -n "${PATCH_EXCLUDE:-}" ] && echo "$matchname" | grep -qE "$PATCH_EXCLUDE"; then
175
+ continue
176
+ fi
177
+ bash "$fix" 2>/dev/null || true
178
+ done
179
+
180
+ echo ""
181
+ }
182
+
183
+ # ── Apply to each discovered install ──
184
+
185
+ for entry in "${INSTALLS[@]}"; do
186
+ IFS=$'\t' read -r scope dist_src version rv_cli rs_root writable <<< "$entry"
187
+ [ "$rv_cli" = "-" ] && rv_cli=""
188
+ [ "$rs_root" = "-" ] && rs_root=""
189
+
190
+ if [ "$writable" = "no" ]; then
191
+ echo "[$scope] SKIP: $dist_src not writable (re-run with sudo)"
192
+ echo ""
193
+ continue
194
+ fi
195
+
196
+ apply_patches "$dist_src" "$rv_cli" "$rs_root" "$scope"
197
+ done
198
+
199
+ echo "[PATCHES] Complete"
@@ -0,0 +1,263 @@
1
+ #!/bin/bash
2
+ # repair-post-init.sh
3
+ # Post-init remediation for projects initialized before patch-all.sh.
4
+ #
5
+ # What it does:
6
+ # 1) Finds a patched @claude-flow/cli helper source (local or global npx cache)
7
+ # 2) Backs up target .claude/helpers (default)
8
+ # 3) Rehydrates helper files into target project
9
+ # 4) Preserves/installs a guidance-aware hook-handler when available
10
+
11
+ set -euo pipefail
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ TARGET_DIR="$(pwd)"
15
+ SOURCE_SCOPE="auto" # auto|local|global
16
+ DO_BACKUP=1
17
+ DRY_RUN=0
18
+ RUN_CHECK=1
19
+
20
+ usage() {
21
+ cat <<'EOF'
22
+ Usage:
23
+ bash repair-post-init.sh [options]
24
+
25
+ Options:
26
+ --target <dir> Target project directory (default: current working directory)
27
+ --source <mode> Source mode: auto|local|global (default: auto)
28
+ --no-backup Skip .claude/helpers backup
29
+ --dry-run Print actions without writing files
30
+ --skip-check Skip check-patches.sh preflight
31
+ -h, --help Show help
32
+
33
+ Examples:
34
+ bash repair-post-init.sh --target ~/src/my-project
35
+ bash repair-post-init.sh --target ~/src/my-project --source global
36
+ bash repair-post-init.sh --target ~/src/my-project --dry-run
37
+ EOF
38
+ }
39
+
40
+ fail() {
41
+ echo "[repair-post-init] ERROR: $*" >&2
42
+ exit 1
43
+ }
44
+
45
+ log() {
46
+ echo "[repair-post-init] $*"
47
+ }
48
+
49
+ run_cmd() {
50
+ if [ "$DRY_RUN" -eq 1 ]; then
51
+ echo "[dry-run] $*"
52
+ return 0
53
+ fi
54
+ "$@"
55
+ }
56
+
57
+ while [[ $# -gt 0 ]]; do
58
+ case "$1" in
59
+ --target)
60
+ TARGET_DIR="${2:-}"
61
+ shift 2
62
+ ;;
63
+ --source)
64
+ SOURCE_SCOPE="${2:-}"
65
+ shift 2
66
+ ;;
67
+ --no-backup)
68
+ DO_BACKUP=0
69
+ shift
70
+ ;;
71
+ --dry-run)
72
+ DRY_RUN=1
73
+ shift
74
+ ;;
75
+ --skip-check)
76
+ RUN_CHECK=0
77
+ shift
78
+ ;;
79
+ -h|--help)
80
+ usage
81
+ exit 0
82
+ ;;
83
+ *)
84
+ fail "Unknown option: $1"
85
+ ;;
86
+ esac
87
+ done
88
+
89
+ if [[ ! "$SOURCE_SCOPE" =~ ^(auto|local|global)$ ]]; then
90
+ fail "Invalid --source value: $SOURCE_SCOPE (expected auto|local|global)"
91
+ fi
92
+
93
+ TARGET_DIR="$(cd "$TARGET_DIR" && pwd)"
94
+ [ -d "$TARGET_DIR" ] || fail "Target directory not found: $TARGET_DIR"
95
+
96
+ if [ "$RUN_CHECK" -eq 1 ]; then
97
+ if [ -x "$SCRIPT_DIR/check-patches.sh" ]; then
98
+ log "Running preflight patch check..."
99
+ bash "$SCRIPT_DIR/check-patches.sh" >/dev/null || {
100
+ log "Patch check failed; applying patches..."
101
+ bash "$SCRIPT_DIR/patch-all.sh" --global >/dev/null
102
+ bash "$SCRIPT_DIR/check-patches.sh" >/dev/null || fail "Patch verification failed"
103
+ }
104
+ else
105
+ log "check-patches.sh not found; skipping preflight"
106
+ fi
107
+ fi
108
+
109
+ find_local_helpers() {
110
+ local base="$1"
111
+ for d in "$base" "$base/.." "$base/../.." "$base/../../.."; do
112
+ if [ -d "$d/node_modules/@claude-flow/cli/.claude/helpers" ]; then
113
+ (cd "$d/node_modules/@claude-flow/cli/.claude/helpers" && pwd)
114
+ return 0
115
+ fi
116
+ done
117
+ return 1
118
+ }
119
+
120
+ find_global_helpers() {
121
+ # Use shared discovery to find all installs, then derive helpers path
122
+ . "$SCRIPT_DIR/lib/discover.sh"
123
+ local first_dist_src=""
124
+ while IFS= read -r line; do
125
+ [ -n "$line" ] || continue
126
+ first_dist_src="${line%% *}"
127
+ break
128
+ done < <(discover_all_cf_installs)
129
+ if [ -n "$first_dist_src" ]; then
130
+ # dist/src -> package root -> .claude/helpers
131
+ local pkg_root
132
+ pkg_root="$(cd "$first_dist_src/../.." 2>/dev/null && pwd)"
133
+ if [ -d "$pkg_root/.claude/helpers" ]; then
134
+ echo "$pkg_root/.claude/helpers"
135
+ return 0
136
+ fi
137
+ fi
138
+ # Fallback: legacy direct glob
139
+ ls -td ~/.npm/_npx/*/node_modules/@claude-flow/cli/.claude/helpers 2>/dev/null | head -1 || true
140
+ }
141
+
142
+ SRC_HELPERS=""
143
+ case "$SOURCE_SCOPE" in
144
+ local)
145
+ SRC_HELPERS="$(find_local_helpers "$TARGET_DIR" || true)"
146
+ ;;
147
+ global)
148
+ SRC_HELPERS="$(find_global_helpers)"
149
+ ;;
150
+ auto)
151
+ SRC_HELPERS="$(find_local_helpers "$TARGET_DIR" || true)"
152
+ if [ -z "$SRC_HELPERS" ]; then
153
+ SRC_HELPERS="$(find_global_helpers)"
154
+ fi
155
+ ;;
156
+ esac
157
+
158
+ [ -n "$SRC_HELPERS" ] || fail "Could not locate @claude-flow/cli/.claude/helpers (source=$SOURCE_SCOPE)"
159
+ [ -d "$SRC_HELPERS" ] || fail "Source helpers directory does not exist: $SRC_HELPERS"
160
+ [ -f "$SRC_HELPERS/intelligence.cjs" ] || fail "Source missing intelligence.cjs: $SRC_HELPERS"
161
+
162
+ TARGET_HELPERS="$TARGET_DIR/.claude/helpers"
163
+ BACKUP_PATH="$TARGET_DIR/.claude/helpers.backup.$(date +%Y%m%d-%H%M%S)"
164
+
165
+ log "Target: $TARGET_DIR"
166
+ log "Source helpers: $SRC_HELPERS"
167
+ log "Target helpers: $TARGET_HELPERS"
168
+
169
+ run_cmd mkdir -p "$TARGET_HELPERS"
170
+
171
+ if [ "$DO_BACKUP" -eq 1 ] && [ -d "$TARGET_HELPERS" ]; then
172
+ if [ "$(ls -A "$TARGET_HELPERS" 2>/dev/null || true)" ]; then
173
+ log "Backing up existing helpers -> $BACKUP_PATH"
174
+ run_cmd cp -a "$TARGET_HELPERS" "$BACKUP_PATH"
175
+ fi
176
+ fi
177
+
178
+ # Copy all helpers except hook-handler.cjs first.
179
+ copied=0
180
+ for src in "$SRC_HELPERS"/*; do
181
+ [ -e "$src" ] || continue
182
+ base="$(basename "$src")"
183
+ if [ "$base" = "hook-handler.cjs" ]; then
184
+ continue
185
+ fi
186
+ run_cmd cp -a "$src" "$TARGET_HELPERS/$base"
187
+ copied=$((copied + 1))
188
+ done
189
+
190
+ # Prefer guidance-aware hook-handler when installed in target project.
191
+ GUIDANCE_HANDLER="$TARGET_DIR/node_modules/claude-flow-guidance-implementation/scaffold/.claude/helpers/hook-handler.cjs"
192
+ HOOK_SRC="$SRC_HELPERS/hook-handler.cjs"
193
+ HOOK_REASON="@claude-flow/cli helper template"
194
+ if [ -f "$GUIDANCE_HANDLER" ]; then
195
+ HOOK_SRC="$GUIDANCE_HANDLER"
196
+ HOOK_REASON="guidance implementation hook-handler"
197
+ fi
198
+
199
+ if [ -f "$HOOK_SRC" ]; then
200
+ log "Installing hook-handler from: $HOOK_REASON"
201
+ run_cmd cp -a "$HOOK_SRC" "$TARGET_HELPERS/hook-handler.cjs"
202
+ copied=$((copied + 1))
203
+ fi
204
+
205
+ # Make guidance hook-handler path resolution work when copied into project .claude/helpers.
206
+ if [ "$DRY_RUN" -eq 0 ] && [ -f "$TARGET_HELPERS/hook-handler.cjs" ]; then
207
+ python3 - "$TARGET_HELPERS/hook-handler.cjs" <<'PY'
208
+ import pathlib
209
+ import sys
210
+
211
+ path = pathlib.Path(sys.argv[1])
212
+ text = path.read_text()
213
+
214
+ start = text.find("function getBundledScriptPath(scriptName)")
215
+ end = text.find("function getGuidanceScriptPath()", start if start >= 0 else 0)
216
+
217
+ if start >= 0 and end > start and "claude-flow-guidance-implementation" not in text:
218
+ replacement = """function getBundledScriptPath(scriptName) {
219
+ return path.join(
220
+ getProjectDir(),
221
+ 'node_modules',
222
+ 'claude-flow-guidance-implementation',
223
+ 'scaffold',
224
+ 'scripts',
225
+ scriptName
226
+ );
227
+ }
228
+
229
+ function resolveGuidanceScriptPath(scriptName) {
230
+ const localPath = path.join(getProjectDir(), 'scripts', scriptName);
231
+ if (fs.existsSync(localPath)) return localPath;
232
+
233
+ const bundledPath = getBundledScriptPath(scriptName);
234
+ if (fs.existsSync(bundledPath)) return bundledPath;
235
+
236
+ // Keep compatibility for handler copies that live under scaffold/.claude/helpers.
237
+ const relativeBundledPath = path.resolve(__dirname, '..', '..', 'scripts', scriptName);
238
+ if (fs.existsSync(relativeBundledPath)) return relativeBundledPath;
239
+
240
+ return localPath;
241
+ }
242
+
243
+ """
244
+ text = text[:start] + replacement + text[end:]
245
+ path.write_text(text)
246
+ PY
247
+ fi
248
+
249
+ if [ "$DRY_RUN" -eq 0 ] && [ -f "$TARGET_HELPERS/hook-handler.cjs" ]; then
250
+ chmod +x "$TARGET_HELPERS/hook-handler.cjs" || true
251
+ fi
252
+
253
+ log "Copied/updated helper files: $copied"
254
+
255
+ if [ "$DRY_RUN" -eq 0 ] && [ -f "$TARGET_HELPERS/hook-handler.cjs" ]; then
256
+ if node "$TARGET_HELPERS/hook-handler.cjs" status >/dev/null 2>&1; then
257
+ log "Smoke check: hook-handler status OK"
258
+ else
259
+ log "WARN: hook-handler status returned non-zero"
260
+ fi
261
+ fi
262
+
263
+ log "Done."
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env node
2
+ // scripts/preflight.mjs — Pre-commit/pre-publish consistency check.
3
+ // Syncs: doc tables, defect counts, version strings across all files.
4
+ // Source of truth: package.json (version), npm/config.json (targets), patch/*/ (defects).
5
+ //
6
+ // Usage: node scripts/preflight.mjs [--check]
7
+ // --check Exit 1 if anything is out of date (for hooks/CI), don't write.
8
+
9
+ import { readFileSync, writeFileSync } from 'node:fs';
10
+ import { resolve, dirname } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { discover } from '../lib/discover.mjs';
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const ROOT = resolve(__dirname, '..');
16
+ const checkOnly = process.argv.includes('--check');
17
+
18
+ const data = discover();
19
+ const { patches, categories, stats } = data;
20
+
21
+ // ── Sources of truth ──
22
+
23
+ const pkgJson = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8'));
24
+ const configJson = JSON.parse(readFileSync(resolve(ROOT, 'npm', 'config.json'), 'utf-8'));
25
+ const pkgVersion = pkgJson.version;
26
+ const cliTarget = configJson.targets['@claude-flow/cli'];
27
+ const swarmTarget = configJson.targets['ruv-swarm'];
28
+
29
+ // ── Helpers ──
30
+
31
+ /** Group patches by prefix, preserving sort order. */
32
+ function groupByPrefix(patches) {
33
+ const groups = new Map();
34
+ for (const p of patches) {
35
+ if (!groups.has(p.prefix)) groups.set(p.prefix, []);
36
+ groups.get(p.prefix).push(p);
37
+ }
38
+ return groups;
39
+ }
40
+
41
+ /**
42
+ * Replace content between marker comments in a file.
43
+ * Returns true if content changed.
44
+ */
45
+ function replaceMarkerSection(filePath, markerName, newContent) {
46
+ const beginMarker = `<!-- GENERATED:${markerName}:begin -->`;
47
+ const endMarker = `<!-- GENERATED:${markerName}:end -->`;
48
+
49
+ const text = readFileSync(filePath, 'utf-8');
50
+ const beginIdx = text.indexOf(beginMarker);
51
+ const endIdx = text.indexOf(endMarker);
52
+
53
+ if (beginIdx < 0 || endIdx < 0) {
54
+ console.error(`ERROR: Markers ${beginMarker} / ${endMarker} not found in ${filePath}`);
55
+ console.error(' Add them around the section that should be auto-generated.');
56
+ process.exit(1);
57
+ }
58
+
59
+ const before = text.slice(0, beginIdx + beginMarker.length);
60
+ const after = text.slice(endIdx);
61
+ const updated = `${before}\n${newContent}\n${after}`;
62
+
63
+ if (updated === text) return false;
64
+
65
+ if (!checkOnly) writeFileSync(filePath, updated);
66
+ return true;
67
+ }
68
+
69
+ /**
70
+ * Replace all occurrences of a version string in a file.
71
+ * Returns true if content changed.
72
+ */
73
+ function syncVersionInFile(filePath, oldVersion, newVersion, label) {
74
+ if (oldVersion === newVersion) return false;
75
+ const text = readFileSync(filePath, 'utf-8');
76
+ if (!text.includes(oldVersion)) return false;
77
+ const updated = text.replaceAll(oldVersion, newVersion);
78
+ if (updated === text) return false;
79
+ if (!checkOnly) writeFileSync(filePath, updated);
80
+ return true;
81
+ }
82
+
83
+ // ── Generate README.md defect index ──
84
+
85
+ function generateReadmeIndex() {
86
+ const groups = groupByPrefix(patches);
87
+ const lines = [`${stats.total} defects across ${stats.categories} categories.`];
88
+
89
+ for (const [prefix, items] of groups) {
90
+ const catLabel = categories[prefix] ?? prefix;
91
+ lines.push('');
92
+ lines.push(`### ${prefix} -- ${catLabel}`);
93
+ lines.push('');
94
+ lines.push('| ID | Description <img width="500" height="1" /> | Severity | GitHub&nbsp;Issue |');
95
+ lines.push('|----|-------------|----------|--------------|');
96
+
97
+ for (const p of items) {
98
+ const idLink = `[${p.id.replace('-', '&#8209;')}](patch/${p.dir}/)`;
99
+ const ghLink = p.githubUrl ? `[${p.github}](${p.githubUrl})` : p.github;
100
+ lines.push(`| ${idLink} | ${p.title} | ${p.severity} | ${ghLink} |`);
101
+ }
102
+ }
103
+
104
+ return lines.join('\n');
105
+ }
106
+
107
+ // ── Generate CLAUDE.md defect tables ──
108
+
109
+ function generateClaudeTables() {
110
+ const groups = groupByPrefix(patches);
111
+ const lines = [];
112
+
113
+ // Category summary table
114
+ lines.push('| Prefix | Category | Count |');
115
+ lines.push('|--------|----------|-------|');
116
+ for (const [prefix, items] of groups) {
117
+ const catLabel = categories[prefix] ?? prefix;
118
+ lines.push(`| ${prefix} | ${catLabel} | ${items.length} |`);
119
+ }
120
+
121
+ // Full defect list
122
+ lines.push('');
123
+ lines.push(`## All ${stats.total} Defects`);
124
+ lines.push('');
125
+ lines.push('| ID | GitHub Issue | Severity |');
126
+ lines.push('|----|-------------|----------|');
127
+ for (const p of patches) {
128
+ const ghText = p.github ? `${p.github} ${p.title}` : p.title;
129
+ const ghLink = p.githubUrl ? `[${ghText}](${p.githubUrl})` : ghText;
130
+ lines.push(`| ${p.id} | ${ghLink} | ${p.severity} |`);
131
+ }
132
+
133
+ return lines.join('\n');
134
+ }
135
+
136
+ // ── Generate npm/README.md defect list ──
137
+
138
+ const REPO_URL = 'https://github.com/sparkling/claude-flow-patch';
139
+
140
+ function generateNpmDefectList() {
141
+ const groups = groupByPrefix(patches);
142
+ const lines = [
143
+ `${stats.total} tracked defects across ${stats.categories} categories.`,
144
+ '',
145
+ '| Defect | Description | GitHub Issue |',
146
+ '|--------|-------------|-------------|',
147
+ ];
148
+
149
+ for (const [, items] of groups) {
150
+ for (const p of items) {
151
+ const defectLink = `[${p.id}](${REPO_URL}/tree/master/patch/${p.dir})`;
152
+ const ghLink = p.githubUrl ? `[${p.github}](${p.githubUrl})` : p.github;
153
+ lines.push(`| ${defectLink} | ${p.title} | ${ghLink} |`);
154
+ }
155
+ }
156
+
157
+ return lines.join('\n');
158
+ }
159
+
160
+ function updateNpmReadme() {
161
+ return replaceMarkerSection(
162
+ resolve(ROOT, 'npm', 'README.md'),
163
+ 'npm-defects',
164
+ generateNpmDefectList()
165
+ );
166
+ }
167
+
168
+ // ── Sync npm/config.json (version + defect counts) ──
169
+
170
+ function updateNpmConfig() {
171
+ const filePath = resolve(ROOT, 'npm', 'config.json');
172
+ const config = JSON.parse(readFileSync(filePath, 'utf-8'));
173
+
174
+ let changed = false;
175
+
176
+ // Sync version.current from package.json
177
+ if (config.version?.current !== pkgVersion) {
178
+ config.version.current = pkgVersion;
179
+ changed = true;
180
+ }
181
+
182
+ // Sync defect counts from discovery
183
+ if (config.defects?.total !== stats.total) {
184
+ config.defects.total = stats.total;
185
+ changed = true;
186
+ }
187
+ if (config.defects?.categories !== stats.categories) {
188
+ config.defects.categories = stats.categories;
189
+ changed = true;
190
+ }
191
+
192
+ if (changed && !checkOnly) {
193
+ writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n');
194
+ }
195
+ return changed;
196
+ }
197
+
198
+ // ── Main ──
199
+
200
+ let anyChanged = false;
201
+
202
+ function report(changed, label) {
203
+ if (!changed) return;
204
+ anyChanged = true;
205
+ console.log(checkOnly ? `STALE: ${label}` : `Updated: ${label}`);
206
+ }
207
+
208
+ // 1. Doc tables (marker-based sections)
209
+ report(
210
+ replaceMarkerSection(resolve(ROOT, 'README.md'), 'defect-index', generateReadmeIndex()),
211
+ 'README.md (defect index)'
212
+ );
213
+ report(
214
+ replaceMarkerSection(resolve(ROOT, 'CLAUDE.md'), 'defect-tables', generateClaudeTables()),
215
+ 'CLAUDE.md (defect tables)'
216
+ );
217
+ report(updateNpmReadme(), 'npm/README.md (defect list)');
218
+
219
+ // 2. Config sync (version + counts)
220
+ report(updateNpmConfig(), 'npm/config.json (version/counts)');
221
+
222
+ // 3. Upstream baseline version in prose (sync from npm/config.json targets)
223
+ // Find any stale version strings and replace with current targets.
224
+ // We scan for the pattern v?X.Y.Z-alpha.N and replace if it doesn't match config.
225
+ const versionFiles = ['README.md', 'CLAUDE.md', 'npm/README.md', 'AGENTS.md'];
226
+ for (const file of versionFiles) {
227
+ const filePath = resolve(ROOT, file);
228
+ let text;
229
+ try { text = readFileSync(filePath, 'utf-8'); } catch { continue; }
230
+
231
+ let updated = text;
232
+
233
+ // Sync @claude-flow/cli version references
234
+ // Match patterns like **v3.1.0-alpha.NN** or `3.1.0-alpha.NN` or @3.1.0-alpha.NN
235
+ const cliRe = /(?<=[@`*v])3\.1\.0-alpha\.\d+/g;
236
+ updated = updated.replace(cliRe, cliTarget);
237
+
238
+ if (updated !== text) {
239
+ if (!checkOnly) writeFileSync(filePath, updated);
240
+ report(true, `${file} (upstream baseline)`);
241
+ }
242
+ }
243
+
244
+ if (!anyChanged) {
245
+ console.log('All files are up to date.');
246
+ } else if (checkOnly) {
247
+ console.log('\nFiles are out of date. Run: npm run preflight');
248
+ process.exit(1);
249
+ }