@sparkleideas/claude-flow-patch 3.1.0-alpha.44.patch.5 → 3.1.0-alpha.44.patch.7

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @sparkleideas/claude-flow-patch
2
2
 
3
- > Runtime patches for `@claude-flow/cli` v3.1.0-alpha.40, `ruvector`, and `ruv-swarm` v1.0.20
3
+ > Runtime patches for `@claude-flow/cli` v3.1.0-alpha.41, `ruvector`, and `ruv-swarm` v1.0.20
4
4
 
5
5
  **Claude Code instructions are in [CLAUDE.md](CLAUDE.md). Project-specific instructions (defect index, workflows, policies) are in [README.md](README.md).**
6
6
 
package/CLAUDE.md CHANGED
@@ -24,6 +24,7 @@ Runtime patches for `@claude-flow/cli` **v3.1.0-alpha.41**, `ruvector`, and `ruv
24
24
  - NEVER reuse a defect ID that was previously assigned to a different GitHub issue
25
25
  - ONE defect directory and ONE fix.py per GitHub issue -- do not combine multiple GitHub issues into one defect or split one GitHub issue across multiple defects
26
26
  - ALWAYS verify with `bash check-patches.sh` after applying
27
+ - ALWAYS run `npm run preflight` before staging — the pre-commit hook (`hooks/pre-commit`) enforces this via `--check` mode
27
28
  - ALWAYS update ALL listing files when adding/removing a defect (see checklist)
28
29
  - Execution order is determined by the numeric prefix on each defect directory name. Dependencies between defects are expressed by assigning lower numbers to prerequisites.
29
30
 
@@ -38,7 +39,7 @@ lib/
38
39
  discover.mjs # Dynamic discovery: scans patch/*/ → structured JSON
39
40
  categories.json # Prefix-to-label mapping (one line per category)
40
41
  scripts/
41
- update-docs.mjs # Regenerates doc tables from discovery (npm run update-docs)
42
+ preflight.mjs # Pre-commit sync: doc tables, versions, config (npm run preflight)
42
43
  upstream-log.mjs # Show recent upstream releases (npm run upstream-log [count])
43
44
  patch/
44
45
  {NNN}-{PREFIX}-{NNN}-{slug}/ # NNN = 3-digit execution order
@@ -90,7 +91,13 @@ gh issue comment <NUMBER> --repo ruvnet/claude-flow --body "$(cat <<'EOF'
90
91
 
91
92
  Defect **{PREFIX}-{NNN}** in [claude-flow-patch](https://github.com/sparkling/claude-flow-patch).
92
93
 
94
+ **Root cause:** <1-2 sentences explaining why the bug occurs at the code level>
95
+
93
96
  <What the patch does. Be specific. Include a table if multiple ops.>
97
+
98
+ **Affected versions:** `@claude-flow/cli` 3.1.0-alpha.44 through current
99
+
100
+ **Related issues:** #NNN, #NNN
94
101
  EOF
95
102
  )"
96
103
  ```
@@ -114,6 +121,12 @@ gh issue create --repo ruvnet/claude-flow \
114
121
 
115
122
  ## Files Affected
116
123
  - <dist/src/path/to/file.js>
124
+
125
+ ## Affected Versions
126
+ `@claude-flow/cli` 3.1.0-alpha.44 through current
127
+
128
+ ## Related Issues
129
+ - #NNN — <short description of relationship>
117
130
  EOF
118
131
  )"
119
132
  ```
@@ -123,6 +136,7 @@ Save the returned GitHub issue number for the defect README.md.
123
136
  ### Comment hygiene:
124
137
 
125
138
  - One comment per defect, describing the patch. No meta-commentary.
139
+ - Every comment/body MUST include affected versions and related issues (use "None" if truly standalone).
126
140
  - If you need to replace a comment, delete the old one first (`gh api -X DELETE`).
127
141
  - Do not reference defect history, deletion/restoration, or internal decisions.
128
142
 
@@ -135,17 +149,17 @@ Save the returned GitHub issue number for the defect README.md.
135
149
  | DM | Daemon & Workers | 6 |
136
150
  | EM | Embeddings & HNSW | 2 |
137
151
  | GV | Ghost Vectors | 1 |
138
- | HK | Hooks | 3 |
152
+ | HK | Hooks | 5 |
139
153
  | HW | Headless Worker | 4 |
140
154
  | IN | Intelligence | 1 |
141
155
  | MM | Memory Management | 1 |
142
156
  | NS | Memory Namespace | 3 |
143
157
  | RS | ruv-swarm | 1 |
144
158
  | RV | RuVector Intelligence | 3 |
145
- | SG | Settings Generator | 2 |
159
+ | SG | Settings Generator | 4 |
146
160
  | UI | Display & Cosmetic | 2 |
147
161
 
148
- ## All 31 Defects
162
+ ## All 35 Defects
149
163
 
150
164
  | ID | GitHub Issue | Severity |
151
165
  |----|-------------|----------|
@@ -162,9 +176,11 @@ Save the returned GitHub issue number for the defect README.md.
162
176
  | HK-001 | [#1155 post-edit hook records file_path as "unknown"](https://github.com/ruvnet/claude-flow/issues/1155) | Medium |
163
177
  | HK-002 | [#1058 MCP hook handlers are stubs that don't persist data](https://github.com/ruvnet/claude-flow/issues/1058) | High |
164
178
  | HK-003 | [#1158 hooks_metrics MCP handler returns hardcoded fake data](https://github.com/ruvnet/claude-flow/issues/1158) | High |
179
+ | HK-004 | [#1175 hooks_session-start ignores daemon.autoStart from settings.json](https://github.com/ruvnet/claude-flow/issues/1175) | High |
180
+ | HK-005 | [#1171 Multiple MCP servers start independent in-process daemons](https://github.com/ruvnet/claude-flow/issues/1171) | Critical |
165
181
  | HW-001 | [#1111 Headless workers hang — stdin pipe never closed](https://github.com/ruvnet/claude-flow/issues/1111) | Critical |
166
182
  | HW-002 | [#1112 Headless failures silently swallowed as success](https://github.com/ruvnet/claude-flow/issues/1112) | High |
167
- | HW-003 | [#1113 Worker scheduling intervals too aggressive](https://github.com/ruvnet/claude-flow/issues/1113) | High |
183
+ | HW-003 | [#1113 Worker scheduling intervals too aggressive + settings ignored](https://github.com/ruvnet/claude-flow/issues/1113) | High |
168
184
  | IN-001 | [#1154 intelligence.cjs is a stub that doesn't actually learn](https://github.com/ruvnet/claude-flow/issues/1154) | Critical |
169
185
  | MM-001 | [#1152 Remove dead persistPath config option](https://github.com/ruvnet/claude-flow/issues/1152) | Low |
170
186
  | NS-001 | [#1123 Discovery ops default to wrong namespace](https://github.com/ruvnet/claude-flow/issues/1123) | Critical |
@@ -178,8 +194,10 @@ Save the returned GitHub issue number for the defect README.md.
178
194
  | SG-003 | [#1169 Init missing helpers for --dual, --minimal, hooks, and upgrade paths](https://github.com/ruvnet/claude-flow/issues/1169) | Critical |
179
195
  | UI-001 | [#1145 intelligence stats crashes on .toFixed()](https://github.com/ruvnet/claude-flow/issues/1145) | Critical |
180
196
  | UI-002 | [#1146 neural status shows "Not loaded"](https://github.com/ruvnet/claude-flow/issues/1146) | Low |
181
- | DM-006 | [#1114 No log rotation — headless execution logs grow unbounded](https://github.com/ruvnet/claude-flow/issues/1114) | Medium |
197
+ | DM-006 | [#1114 No log rotation — logs grow unbounded](https://github.com/ruvnet/claude-flow/issues/1114) | Medium |
182
198
  | HW-004 | [#1117 runWithTimeout rejects but does not kill child process](https://github.com/ruvnet/claude-flow/issues/1117) | Medium |
199
+ | SG-004 | [#1181 init wizard lacks parity with init](https://github.com/ruvnet/claude-flow/issues/1181) | High |
200
+ | SG-005 | [#1177 add 'start all' subcommand to start everything at once](https://github.com/ruvnet/claude-flow/issues/1177) | Enhancement |
183
201
  <!-- GENERATED:defect-tables:end -->
184
202
 
185
203
  ---
@@ -305,6 +323,8 @@ Both are idempotent: skip if `new` already present, warn if `old` not found.
305
323
  | `SETTINGS_GEN` | `init/settings-generator.js` | @claude-flow/cli |
306
324
  | `HELPERS_GEN` | `init/helpers-generator.js` | @claude-flow/cli |
307
325
  | `EXECUTOR` | `init/executor.js` | @claude-flow/cli |
326
+ | `INIT_CMD` | `commands/init.js` | @claude-flow/cli |
327
+ | `START_CMD` | `commands/start.js` | @claude-flow/cli |
308
328
  | `ruvector_cli` | `bin/cli.js` | ruvector |
309
329
 
310
330
  To target a new file, add a variable to `lib/common.py` following the existing pattern.
@@ -313,7 +333,7 @@ To target a new file, add a variable to `lib/common.py` following the existing p
313
333
 
314
334
  ```bash
315
335
  # Regenerate all documentation from dynamic discovery
316
- npm run update-docs
336
+ npm run preflight
317
337
 
318
338
  # Apply -- should show "Applied: ..."
319
339
  bash patch-all.sh --global
@@ -339,7 +359,7 @@ npm test
339
359
  - [ ] `patch/{PREFIX}-{NNN}-{slug}/sentinel` created with verification directives
340
360
  - [ ] Path variable added to `lib/common.py` (if targeting a new file)
341
361
  - [ ] If new category prefix: add one line to `lib/categories.json`
342
- - [ ] `npm run update-docs` regenerates all doc tables
362
+ - [ ] `npm run preflight` regenerates all doc tables
343
363
  - [ ] `bash patch-all.sh` applies successfully
344
364
  - [ ] `bash patch-all.sh` is idempotent (0 applied on re-run)
345
365
  - [ ] `bash check-patches.sh` shows OK
@@ -355,7 +375,7 @@ Before removing any defect:
355
375
  1. Confirm the bug is genuinely fixed upstream or the patch is truly unreachable.
356
376
  2. Do NOT remove a defect just because a local workaround exists -- the MCP-level patch may still be needed.
357
377
  3. If removing, retire the defect ID permanently. Never reassign a deleted ID to a different GitHub issue.
358
- 4. Run `npm run update-docs` to regenerate all documentation.
378
+ 4. Run `npm run preflight` to regenerate all documentation.
359
379
 
360
380
  ---
361
381
 
@@ -450,6 +470,31 @@ Two dependency chains exist:
450
470
 
451
471
  All other patches are independent.
452
472
 
473
+ ## Preflight & Pre-Commit Hook
474
+
475
+ A git pre-commit hook at `hooks/pre-commit` runs automatically on every commit. It calls `npm run preflight:check` (read-only) and `npm test`. If anything is stale or tests fail, the commit is blocked.
476
+
477
+ **Setup** (one-time, already done for this clone):
478
+ ```bash
479
+ git config core.hooksPath hooks
480
+ ```
481
+
482
+ **Before staging**, run:
483
+ ```bash
484
+ npm run preflight # Syncs doc tables, defect counts, version strings, config
485
+ npm test # Runs all tests
486
+ ```
487
+
488
+ Then `git add -u` to stage the regenerated files.
489
+
490
+ **What `preflight` syncs**:
491
+ - Defect tables in README.md, CLAUDE.md, npm/README.md (from `patch/*/README.md`)
492
+ - Defect counts in `npm/config.json` (from discovery)
493
+ - `npm/config.json` version.current (from `package.json`)
494
+ - Upstream baseline version in prose (from `npm/config.json` targets)
495
+
496
+ Manual edits to generated sections (`<!-- GENERATED:*:begin/end -->`) will be overwritten.
497
+
453
498
  ## Key Design Decisions
454
499
 
455
500
  - **Idempotent**: `patch()` checks if `new` string is already present before replacing.
package/README.md CHANGED
@@ -158,7 +158,7 @@ claude-flow-patch/
158
158
  discover.mjs # Dynamic patch discovery — single source of truth
159
159
  categories.json # Prefix-to-label mapping (e.g. HW → Headless Worker)
160
160
  scripts/
161
- update-docs.mjs # Regenerate doc tables from discover() output
161
+ preflight.mjs # Pre-commit sync: doc tables, versions, config
162
162
  patch/
163
163
  {NNN}-{PREFIX}-{NNN}-{slug}/ # NNN = 3-digit execution order
164
164
  README.md # Defect report: title, severity, root cause, fix
@@ -173,7 +173,7 @@ claude-flow-patch/
173
173
  ## Defect Index
174
174
 
175
175
  <!-- GENERATED:defect-index:begin -->
176
- 31 defects across 13 categories.
176
+ 35 defects across 13 categories.
177
177
 
178
178
  ### CF -- Config & Doctor
179
179
 
@@ -191,7 +191,7 @@ claude-flow-patch/
191
191
  | [DM&#8209;003](patch/050-DM-003-macos-freemem/) | macOS freemem() always ~0% — workers blocked | Critical | [#1077](https://github.com/ruvnet/claude-flow/issues/1077) |
192
192
  | [DM&#8209;004](patch/060-DM-004-preload-worker-stub/) | Preload worker stub + missing from defaults | Enhancement | [#1139](https://github.com/ruvnet/claude-flow/issues/1139) |
193
193
  | [DM&#8209;005](patch/070-DM-005-consolidation-worker-stub/) | Consolidation worker stub (no decay/rebuild) | Enhancement | [#1140](https://github.com/ruvnet/claude-flow/issues/1140) |
194
- | [DM&#8209;006](patch/300-DM-006-log-rotation/) | No log rotation — headless execution logs grow unbounded | Medium | [#1114](https://github.com/ruvnet/claude-flow/issues/1114) |
194
+ | [DM&#8209;006](patch/300-DM-006-log-rotation/) | No log rotation — logs grow unbounded | Medium | [#1114](https://github.com/ruvnet/claude-flow/issues/1114) |
195
195
 
196
196
  ### EM -- Embeddings & HNSW
197
197
 
@@ -213,6 +213,8 @@ claude-flow-patch/
213
213
  | [HK&#8209;001](patch/110-HK-001-post-edit-file-path/) | post-edit hook records file_path as "unknown" | Medium | [#1155](https://github.com/ruvnet/claude-flow/issues/1155) |
214
214
  | [HK&#8209;002](patch/120-HK-002-hooks-tools-stub/) | MCP hook handlers are stubs that don't persist data | High | [#1058](https://github.com/ruvnet/claude-flow/issues/1058) |
215
215
  | [HK&#8209;003](patch/130-HK-003-metrics-hardcoded/) | hooks_metrics MCP handler returns hardcoded fake data | High | [#1158](https://github.com/ruvnet/claude-flow/issues/1158) |
216
+ | [HK&#8209;004](patch/135-HK-004-respect-daemon-autostart/) | hooks_session-start ignores daemon.autoStart from settings.json | High | [#1175](https://github.com/ruvnet/claude-flow/issues/1175) |
217
+ | [HK&#8209;005](patch/137-HK-005-daemon-pid-guard/) | Multiple MCP servers start independent in-process daemons | Critical | [#1171](https://github.com/ruvnet/claude-flow/issues/1171) |
216
218
 
217
219
  ### HW -- Headless Worker
218
220
 
@@ -220,7 +222,7 @@ claude-flow-patch/
220
222
  |----|-------------|----------|--------------|
221
223
  | [HW&#8209;001](patch/140-HW-001-stdin-hang/) | Headless workers hang — stdin pipe never closed | Critical | [#1111](https://github.com/ruvnet/claude-flow/issues/1111) |
222
224
  | [HW&#8209;002](patch/150-HW-002-failures-swallowed/) | Headless failures silently swallowed as success | High | [#1112](https://github.com/ruvnet/claude-flow/issues/1112) |
223
- | [HW&#8209;003](patch/160-HW-003-aggressive-intervals/) | Worker scheduling intervals too aggressive | High | [#1113](https://github.com/ruvnet/claude-flow/issues/1113) |
225
+ | [HW&#8209;003](patch/160-HW-003-aggressive-intervals/) | Worker scheduling intervals too aggressive + settings ignored | High | [#1113](https://github.com/ruvnet/claude-flow/issues/1113) |
224
226
  | [HW&#8209;004](patch/310-HW-004-runwithtimeout-orphan/) | runWithTimeout rejects but does not kill child process | Medium | [#1117](https://github.com/ruvnet/claude-flow/issues/1117) |
225
227
 
226
228
  ### IN -- Intelligence
@@ -263,6 +265,8 @@ claude-flow-patch/
263
265
  |----|-------------|----------|--------------|
264
266
  | [SG&#8209;001](patch/260-SG-001-init-settings/) | Init generates invalid settings | High | [#1150](https://github.com/ruvnet/claude-flow/issues/1150) |
265
267
  | [SG&#8209;003](patch/270-SG-003-init-helpers-all-paths/) | Init missing helpers for --dual, --minimal, hooks, and upgrade paths | Critical | [#1169](https://github.com/ruvnet/claude-flow/issues/1169) |
268
+ | [SG&#8209;004](patch/320-SG-004-wizard-parity/) | init wizard lacks parity with init | High | [#1181](https://github.com/ruvnet/claude-flow/issues/1181) |
269
+ | [SG&#8209;005](patch/330-SG-005-start-all-subcommand/) | add 'start all' subcommand to start everything at once | Enhancement | [#1177](https://github.com/ruvnet/claude-flow/issues/1177) |
266
270
 
267
271
  ### UI -- Display & Cosmetic
268
272
 
package/check-patches.sh CHANGED
@@ -165,10 +165,14 @@ if [ -x "$SCRIPT_DIR/patch-all.sh" ]; then
165
165
  if [[ -n "$TARGET_DIR" ]]; then REAPPLY_ARGS+=(--target "$TARGET_DIR"); fi
166
166
  bash "$SCRIPT_DIR/patch-all.sh" "${REAPPLY_ARGS[@]}"
167
167
  echo ""
168
- echo "[PATCHES] Auto-reapplied. Restarting daemon..."
168
+ echo "[PATCHES] Auto-reapplied. Stopping existing daemons..."
169
169
  npx @claude-flow/cli@latest daemon stop 2>/dev/null
170
+ # Fallback: kill by PID file if daemon stop missed an orphan (project-scoped, not global)
171
+ _pid=$(cat .claude-flow/daemon.pid 2>/dev/null)
172
+ if [ -n "$_pid" ]; then kill "$_pid" 2>/dev/null || true; rm -f .claude-flow/daemon.pid; fi
173
+ sleep 1
170
174
  npx @claude-flow/cli@latest daemon start 2>/dev/null
171
- echo "[PATCHES] Daemon restarted with patched code."
175
+ echo "[PATCHES] Daemon restarted in background (PID: $(cat .claude-flow/daemon.pid 2>/dev/null || echo 'unknown'))"
172
176
  echo ""
173
177
  else
174
178
  echo "[PATCHES] ERROR: patch-all.sh not found at $SCRIPT_DIR"
package/lib/common.py CHANGED
@@ -83,6 +83,7 @@ HELPERS_GEN = init + "/helpers-generator.js" if init else ""
83
83
  EXECUTOR = init + "/executor.js" if init else ""
84
84
  TYPES = init + "/types.js" if init else ""
85
85
  INIT_CMD = commands + "/init.js" if commands else ""
86
+ START_CMD = commands + "/start.js" if commands else ""
86
87
 
87
88
  # Source helpers (shipped with package, copied by writeHelpers when source dir found)
88
89
  _pkg_root = os.path.dirname(os.path.dirname(base)) if base else ""
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "@sparkleideas/claude-flow-patch",
3
- "version": "3.1.0-alpha.44.patch.5",
3
+ "version": "3.1.0-alpha.44.patch.7",
4
4
  "description": "Patch toolkit for @claude-flow/cli init/runtime defects with verify and post-init repair commands",
5
5
  "scripts": {
6
+ "preflight": "node scripts/preflight.mjs",
7
+ "preflight:check": "node scripts/preflight.mjs --check",
8
+ "prepublishOnly": "npm run preflight && npm test",
6
9
  "test": "node --test tests/*.test.mjs",
7
10
  "package": "bash npm/package.sh",
8
11
  "package:dry": "bash npm/package.sh --dry-run",
9
12
  "publish:npm": "bash npm/publish.sh",
10
13
  "publish:dry": "bash npm/publish.sh --dry-run",
11
- "update-docs": "node scripts/update-docs.mjs",
12
- "update-docs:check": "node scripts/update-docs.mjs --check",
13
14
  "upstream-log": "node scripts/upstream-log.mjs",
14
15
  "guidance:analyze": "guidance analyze",
15
16
  "guidance:optimize": "guidance autopilot --once --apply --source manual",
@@ -0,0 +1,11 @@
1
+ # HK-004: hooks_session-start ignores daemon.autoStart from settings.json
2
+ **Severity**: High
3
+ **GitHub**: [#1175](https://github.com/ruvnet/claude-flow/issues/1175)
4
+ ## Root Cause
5
+ `hooks_session-start` handler in `hooks-tools.js` (line ~1216) determines daemon auto-start solely from the MCP call parameter: `const shouldStartDaemon = params.startDaemon !== false;`. It never reads `claudeFlow.daemon.autoStart` from `.claude/settings.json`. Setting `autoStart: false` in settings.json has no effect — the daemon always starts on session-start.
6
+ ## Fix
7
+ Wrap the `shouldStartDaemon` assignment in an IIFE that first checks the MCP parameter, then reads settings.json. If `claudeFlow.daemon.autoStart === false`, returns false. Falls back to true on any read/parse error.
8
+ ## Files Patched
9
+ - mcp-tools/hooks-tools.js
10
+ ## Ops
11
+ 1 op in fix.py
@@ -0,0 +1,14 @@
1
+ # HK-004: hooks_session-start ignores daemon.autoStart from settings.json
2
+ # GitHub: #1175
3
+ patch("HK-004: respect daemon autoStart setting",
4
+ MCP_HOOKS,
5
+ " const shouldStartDaemon = params.startDaemon !== false;",
6
+ """ const shouldStartDaemon = (() => {
7
+ if (params.startDaemon === false) return false;
8
+ try {
9
+ const sp = join(process.cwd(), '.claude', 'settings.json');
10
+ const s = JSON.parse(readFileSync(sp, 'utf-8'));
11
+ if (s?.claudeFlow?.daemon?.autoStart === false) return false;
12
+ } catch { /* no settings or unreadable — default to true */ }
13
+ return true;
14
+ })();""")
@@ -0,0 +1 @@
1
+ grep "claudeFlow?.daemon?.autoStart" mcp-tools/hooks-tools.js
@@ -0,0 +1,11 @@
1
+ # HK-005: Multiple MCP servers start independent in-process daemons
2
+ **Severity**: Critical
3
+ **GitHub**: [#1171](https://github.com/ruvnet/claude-flow/issues/1171)
4
+ ## Root Cause
5
+ `hooks_session-start` calls `startDaemon()` (worker-daemon.js) which creates an in-process `WorkerDaemon` singleton per Node.js process. Each MCP server is a separate process with its own singleton — no cross-process coordination. The CLI background daemon path (`daemon.js`) has PID-file coordination but the MCP hook path bypasses it entirely. Result: N MCP servers per project = N daemon instances = N × 6 workers.
6
+ ## Fix
7
+ Add PID-file guard to the MCP hook path using `.claude-flow/daemon.pid` (same file the CLI path uses). Before calling `startDaemon()`, check PID file: if a different process owns it and is alive, skip (reuse). If the PID is our own process or stale, proceed and overwrite. No cleanup on session-end — stale PIDs self-heal via `kill(pid, 0)` on next start. The PID file becomes a universal one-daemon-per-project lock across both MCP and CLI paths.
8
+ ## Files Patched
9
+ - mcp-tools/hooks-tools.js
10
+ ## Ops
11
+ 2 ops in fix.py
@@ -0,0 +1,53 @@
1
+ # HK-005: Multiple MCP servers start independent in-process daemons
2
+ # No cross-process coordination on the hooks_session-start path.
3
+ # GitHub: #1171
4
+
5
+ # Op 1: Add PID-file guard before startDaemon()
6
+ patch("HK-005a: cross-process daemon PID guard",
7
+ MCP_HOOKS,
8
+ """ // Auto-start daemon if enabled
9
+ let daemonStatus = { started: false };
10
+ if (shouldStartDaemon) {
11
+ try {
12
+ // Dynamic import to avoid circular dependencies
13
+ const { startDaemon } = await import('../services/worker-daemon.js');
14
+ const daemon = await startDaemon(process.cwd());""",
15
+ """ // Auto-start daemon if enabled
16
+ let daemonStatus = { started: false };
17
+ if (shouldStartDaemon) {
18
+ try {
19
+ // HK-005: PID-file guard — one daemon per project across processes
20
+ const _pidDir = join(process.cwd(), '.claude-flow');
21
+ const _pidPath = join(_pidDir, 'daemon.pid');
22
+ let _skipDaemon = false;
23
+ try {
24
+ const _xPid = parseInt(readFileSync(_pidPath, 'utf-8').trim(), 10);
25
+ if (!isNaN(_xPid) && _xPid !== process.pid) {
26
+ try { process.kill(_xPid, 0); _skipDaemon = true; daemonStatus = { started: true, pid: _xPid, reused: true }; }
27
+ catch { /* stale PID from dead process — proceed */ }
28
+ }
29
+ } catch { /* no PID file — proceed */ }
30
+ if (!_skipDaemon) {
31
+ // Dynamic import to avoid circular dependencies
32
+ const { startDaemon } = await import('../services/worker-daemon.js');
33
+ const daemon = await startDaemon(process.cwd());""")
34
+
35
+ # Op 2: Write PID after successful start + close guard block
36
+ patch("HK-005b: write PID after daemon start",
37
+ MCP_HOOKS,
38
+ """ const status = daemon.getStatus();
39
+ daemonStatus = {
40
+ started: true,
41
+ pid: status.pid,
42
+ };""",
43
+ """ const status = daemon.getStatus();
44
+ // HK-005: Write PID so other processes detect this daemon
45
+ try {
46
+ if (!existsSync(_pidDir)) { mkdirSync(_pidDir, { recursive: true }); }
47
+ writeFileSync(_pidPath, String(status.pid || process.pid));
48
+ } catch { /* best-effort */ }
49
+ daemonStatus = {
50
+ started: true,
51
+ pid: status.pid,
52
+ };
53
+ } // end HK-005 guard""")
@@ -0,0 +1,2 @@
1
+ grep "HK-005: PID-file guard" mcp-tools/hooks-tools.js
2
+ grep "HK-005: Write PID" mcp-tools/hooks-tools.js
@@ -1,11 +1,11 @@
1
- # HW-003: Worker scheduling intervals too aggressive
1
+ # HW-003: Worker scheduling intervals too aggressive + settings ignored
2
2
  **Severity**: High
3
3
  **GitHub**: [#1113](https://github.com/ruvnet/claude-flow/issues/1113)
4
4
  ## Root Cause
5
- `DEFAULT_WORKERS` uses pre-headless intervals (audit: 10m, optimize: 15m, testgaps: 20m). ADR-020 specifies longer intervals (30/60/60m) for headless workers that invoke Claude.
5
+ `DEFAULT_WORKERS` uses pre-headless intervals (audit: 10m, optimize: 15m, testgaps: 20m). ADR-020 specifies longer intervals (30/60/60m) for headless workers that invoke Claude. Additionally, `daemon.schedules` from `.claude/settings.json` is never read — user-configured intervals are completely ignored.
6
6
  ## Fix
7
- Align intervals to ADR-020: audit 30m, optimize 60m, testgaps 60m.
7
+ (A) Align hardcoded intervals to ADR-020: audit 30m, optimize 60m, testgaps 60m. (B) After setting default workers, read `claudeFlow.daemon.schedules` from `.claude/settings.json` and merge user-configured intervals/enabled flags into matching workers. Supports string formats ("1h", "30m", "10s") and raw milliseconds.
8
8
  ## Files Patched
9
9
  - services/worker-daemon.js
10
10
  ## Ops
11
- 3 ops in fix.py
11
+ 4 ops in fix.py
@@ -14,3 +14,39 @@ patch("3: testgaps 60m",
14
14
  WD,
15
15
  "type: 'testgaps', intervalMs: 20 * 60 * 1000",
16
16
  "type: 'testgaps', intervalMs: 60 * 60 * 1000")
17
+
18
+ # HW-003 extension: Read daemon.schedules from .claude/settings.json
19
+ # and merge user-configured intervals into worker defaults
20
+ patch("3: settings-driven intervals",
21
+ WD,
22
+ " workers: config?.workers ?? DEFAULT_WORKERS,",
23
+ """ workers: (() => {
24
+ const base = config?.workers ?? DEFAULT_WORKERS;
25
+ try {
26
+ const sp = join(projectRoot, '.claude', 'settings.json');
27
+ const s = JSON.parse(readFileSync(sp, 'utf-8'));
28
+ const schedules = s?.claudeFlow?.daemon?.schedules;
29
+ if (!schedules || typeof schedules !== 'object') return base;
30
+ const parseInterval = (v) => {
31
+ if (typeof v === 'number') return v;
32
+ if (typeof v !== 'string') return null;
33
+ const m = v.match(/^(\\d+(?:\\.\\d+)?)\\s*(ms|s|m|h)$/i);
34
+ if (!m) return null;
35
+ const n = parseFloat(m[1]);
36
+ switch (m[2].toLowerCase()) {
37
+ case 'ms': return n;
38
+ case 's': return n * 1000;
39
+ case 'm': return n * 60 * 1000;
40
+ case 'h': return n * 3600 * 1000;
41
+ default: return null;
42
+ }
43
+ };
44
+ return base.map(w => {
45
+ const sched = schedules[w.type];
46
+ if (!sched) return w;
47
+ const iv = parseInterval(sched.interval ?? sched.intervalMs);
48
+ const en = typeof sched.enabled === 'boolean' ? sched.enabled : w.enabled;
49
+ return { ...w, ...(iv !== null ? { intervalMs: iv } : {}), enabled: en };
50
+ });
51
+ } catch { return base; }
52
+ })(),""")
@@ -1 +1,3 @@
1
- grep "30 * 60 * 1000" services/worker-daemon.js
1
+ grep "intervalMs: 30" services/worker-daemon.js
2
+ grep "parseInterval" services/worker-daemon.js
3
+ grep "daemon?.schedules" services/worker-daemon.js
@@ -1,11 +1,12 @@
1
- # DM-006: No log rotation — headless execution logs grow unbounded
1
+ # DM-006: No log rotation — logs grow unbounded
2
2
  **Severity**: Medium
3
3
  **GitHub**: [#1114](https://github.com/ruvnet/claude-flow/issues/1114)
4
4
  ## Root Cause
5
- `logExecution()` in `headless-worker-executor.js` creates 2-3 log files per worker run (~75 KB each) but has zero cleanup. No rotation, no max file count, no TTL. At current daemon intervals this accumulates ~23 MB/day, ~702 MB/month.
5
+ (Headless) `logExecution()` in `headless-worker-executor.js` creates 2-3 log files per worker run (~75 KB each) but has zero cleanup. No rotation, no max file count, no TTL. At current daemon intervals this accumulates ~23 MB/day, ~702 MB/month. (Main) `startBackgroundDaemon()` in `daemon.js` opens `daemon.log` in append mode and never truncates. A single long-running daemon can grow daemon.log to 100+ GB.
6
6
  ## Fix
7
- (A) Add `unlinkSync` and `statSync` to the ESM import. (B) Call `cleanupOldLogs()` from `ensureLogDir()` so cleanup runs on each execution cycle. (C) Add `cleanupOldLogs()` method: removes `.log` files older than 7 days or beyond a 500-file cap, keeping newest files.
7
+ (A) Add `unlinkSync` and `statSync` to the headless executor ESM import. (B) Call `cleanupOldLogs()` from `ensureLogDir()` so cleanup runs on each headless execution cycle. (C) Add `cleanupOldLogs()` method: removes `.log` files older than 7 days or beyond a 500-file cap, keeping newest files. (D) In `daemon.js`, before opening `daemon.log` for append, check its size with `fs.statSync()` and rotate to `daemon.log.1` if > 50MB.
8
8
  ## Files Patched
9
9
  - services/headless-worker-executor.js
10
+ - commands/daemon.js
10
11
  ## Ops
11
- 3 ops in fix.py
12
+ 4 ops in fix.py
@@ -56,3 +56,17 @@ patch("DM-006c: add cleanupOldLogs method",
56
56
  catch { /* ignore cleanup errors */ }
57
57
  }
58
58
  logExecution(executionId, type, content) {""")
59
+
60
+ # DM-006 extension: Main daemon.log rotation in daemon.js
61
+ # Before opening daemon.log for append, check size and rotate if > 50MB
62
+ patch("DM-006d: main daemon.log rotation",
63
+ DJ,
64
+ " const logFile = join(logsDir, 'daemon.log');",
65
+ """ const logFile = join(logsDir, 'daemon.log');
66
+ // Rotate main daemon.log if > 50MB
67
+ try {
68
+ const logStat = fs.statSync(logFile);
69
+ if (logStat.size > 50 * 1024 * 1024) {
70
+ fs.renameSync(logFile, logFile + '.1');
71
+ }
72
+ } catch { /* file doesn't exist yet or stat failed — ignore */ }""")
@@ -1 +1,2 @@
1
1
  grep "cleanupOldLogs" services/headless-worker-executor.js
2
+ grep "Rotate main daemon.log" commands/daemon.js
@@ -0,0 +1,32 @@
1
+ # SG-004: init wizard lacks parity with init
2
+
3
+ **Severity**: High
4
+ **GitHub**: [#1181](https://github.com/ruvnet/claude-flow/issues/1181)
5
+
6
+ ## Root Cause
7
+
8
+ The `wizardCommand.action` in `commands/init.js` was implemented as a
9
+ standalone code path that diverges from the parent `initAction`. It skips the
10
+ already-initialized guard, ignores `--force`, `--start-all`, `--start-daemon`,
11
+ `--codex`, and `--dual` flags, and never shows "Next steps" hints. The wizard
12
+ is conceptually "init with interactive value selection" but behaves as a
13
+ completely separate command.
14
+
15
+ ## Fix
16
+
17
+ Four ops bring the wizard to full parity with `init`:
18
+
19
+ | Op | What it does |
20
+ |----|-------------|
21
+ | SG-004a | Adds the already-initialized guard before prompts + passes `--force` to options |
22
+ | SG-004b | Adds `--codex`/`--dual` handling after executeInit succeeds |
23
+ | SG-004c | Adds `--start-all`/`--start-daemon` service startup + "Next steps" hints |
24
+ | SG-004d | Fixes catch block — catches errors cleanly instead of re-throwing |
25
+
26
+ ## Files Patched
27
+
28
+ - `commands/init.js`
29
+
30
+ ## Ops
31
+
32
+ 4 ops in fix.py
@@ -0,0 +1,133 @@
1
+ # SG-004: init wizard lacks parity with init
2
+ # GitHub: #1181
3
+ #
4
+ # The wizard was implemented as a standalone code path that ignores
5
+ # --force, --start-all, --start-daemon, --codex, --dual, and skips
6
+ # the already-initialized guard and "Next steps" hints.
7
+ #
8
+ # 4 ops: init-guard + force (a), codex/dual (b), start-all + next-steps (c),
9
+ # catch-block error handling (d)
10
+
11
+ # Op 1: Add already-initialized guard + pass --force to executeInit options
12
+ patch("SG-004a: wizard init-guard + --force",
13
+ INIT_CMD,
14
+ """ try {
15
+ // Start with base options
16
+ const options = { ...DEFAULT_INIT_OPTIONS, targetDir: ctx.cwd };""",
17
+ """ try {
18
+ // SG-004: Check if already initialized (respects --force)
19
+ const force = ctx.flags.force;
20
+ const initialized = isInitialized(ctx.cwd);
21
+ const hasExisting = initialized.claude || initialized.claudeFlow;
22
+ if (hasExisting && !force) {
23
+ output.printWarning('Claude Flow appears to be already initialized');
24
+ if (initialized.claude) output.printInfo(' Found: .claude/settings.json');
25
+ if (initialized.claudeFlow) output.printInfo(' Found: .claude-flow/config.yaml');
26
+ output.printInfo('Use --force to reinitialize');
27
+ const proceed = await confirm({
28
+ message: 'Do you want to reinitialize? This will overwrite existing configuration.',
29
+ default: false,
30
+ });
31
+ if (!proceed) {
32
+ return { success: true, message: 'Wizard cancelled' };
33
+ }
34
+ }
35
+ // Start with base options
36
+ const options = { ...DEFAULT_INIT_OPTIONS, targetDir: ctx.cwd, force: ctx.flags.force };""")
37
+
38
+ # Op 2: Add --codex / --dual handling after executeInit succeeds
39
+ patch("SG-004b: wizard --codex/--dual support",
40
+ INIT_CMD,
41
+ """ spinner.succeed('Setup complete!');
42
+ // Initialize embeddings if enabled
43
+ let embeddingsInitialized = false;""",
44
+ """ spinner.succeed('Setup complete!');
45
+ // SG-004: Respect --codex / --dual in wizard
46
+ const codexMode = ctx.flags.codex;
47
+ const dualMode = ctx.flags.dual;
48
+ if (codexMode || dualMode) {
49
+ try {
50
+ output.writeln(output.dim(' Initializing Codex integration...'));
51
+ await initCodexAction(ctx, { codexMode, dualMode, force: ctx.flags.force, minimal: false, full: false });
52
+ } catch (err) {
53
+ output.printWarning(`Codex initialization: ${err instanceof Error ? err.message : String(err)}`);
54
+ }
55
+ }
56
+ // Initialize embeddings if enabled
57
+ let embeddingsInitialized = false;""")
58
+
59
+ # Op 3: Add --start-all / --start-daemon + "Next steps" hints before final return
60
+ patch("SG-004c: wizard --start-all + next-steps",
61
+ INIT_CMD,
62
+ """ });
63
+ return { success: true, data: result };
64
+ }
65
+ catch (error) {
66
+ if (error instanceof Error && error.message === 'User cancelled') {""",
67
+ """ });
68
+ // SG-004: Respect --start-all / --start-daemon in wizard
69
+ const startAll = ctx.flags['start-all'] || ctx.flags.startAll;
70
+ const startDaemon = ctx.flags['start-daemon'] || ctx.flags.startDaemon || startAll;
71
+ if (startDaemon || startAll) {
72
+ output.writeln();
73
+ output.printInfo('Starting services...');
74
+ const { execSync } = await import('child_process');
75
+ if (startAll) {
76
+ try {
77
+ output.writeln(output.dim(' Initializing memory database...'));
78
+ execSync('npx @claude-flow/cli@latest memory init 2>/dev/null', {
79
+ stdio: 'pipe', cwd: ctx.cwd, timeout: 30000
80
+ });
81
+ output.writeln(output.success(' \\u2713 Memory initialized'));
82
+ } catch { output.writeln(output.dim(' Memory database already exists')); }
83
+ }
84
+ if (startDaemon) {
85
+ try {
86
+ output.writeln(output.dim(' Starting daemon...'));
87
+ execSync('npx @claude-flow/cli@latest daemon start 2>/dev/null &', {
88
+ stdio: 'pipe', cwd: ctx.cwd, timeout: 10000
89
+ });
90
+ output.writeln(output.success(' \\u2713 Daemon started'));
91
+ } catch { output.writeln(output.warning(' Daemon may already be running')); }
92
+ }
93
+ if (startAll) {
94
+ try {
95
+ output.writeln(output.dim(' Initializing swarm...'));
96
+ execSync('npx @claude-flow/cli@latest swarm init --topology hierarchical 2>/dev/null', {
97
+ stdio: 'pipe', cwd: ctx.cwd, timeout: 30000
98
+ });
99
+ output.writeln(output.success(' \\u2713 Swarm initialized'));
100
+ } catch { output.writeln(output.dim(' Swarm initialization skipped')); }
101
+ }
102
+ output.writeln();
103
+ output.printSuccess('All services started');
104
+ }
105
+ else {
106
+ output.writeln(output.bold('Next steps:'));
107
+ output.printList([
108
+ `Run ${output.highlight('claude-flow daemon start')} to start background workers`,
109
+ `Run ${output.highlight('claude-flow memory init')} to initialize memory database`,
110
+ `Run ${output.highlight('claude-flow swarm init')} to initialize a swarm`,
111
+ `Or re-run with ${output.highlight('--start-all')} to do all of the above`,
112
+ ]);
113
+ }
114
+ return { success: true, data: result };
115
+ }
116
+ catch (error) {
117
+ if (error instanceof Error && error.message === 'User cancelled') {""")
118
+
119
+ # Op 4: Fix wizard catch block — re-throws instead of clean error message
120
+ # spinner is declared inside the try block so we can't call spinner.fail() here
121
+ patch("SG-004d: wizard catch block handles errors cleanly",
122
+ INIT_CMD,
123
+ """ if (error instanceof Error && error.message === 'User cancelled') {
124
+ output.printInfo('Setup cancelled');
125
+ return { success: true };
126
+ }
127
+ throw error;""",
128
+ """ if (error instanceof Error && error.message === 'User cancelled') {
129
+ output.printInfo('Setup cancelled');
130
+ return { success: true };
131
+ }
132
+ output.printError(`Failed to initialize: ${error instanceof Error ? error.message : String(error)}`);
133
+ return { success: false, exitCode: 1 };""")
@@ -0,0 +1 @@
1
+ grep "SG-004" commands/init.js
@@ -0,0 +1,29 @@
1
+ # SG-005: add 'start all' subcommand to start everything at once
2
+
3
+ **Severity**: Enhancement
4
+ **GitHub**: [#1177](https://github.com/ruvnet/claude-flow/issues/1177)
5
+
6
+ ## Root Cause
7
+
8
+ There is no single command to start the full Claude Flow stack (memory +
9
+ daemon + swarm + MCP) on an already-initialized project. `claude-flow start`
10
+ only initializes the swarm and MCP server. Users must run `memory init`,
11
+ `daemon start`, and `start` separately, or re-run `init --start-all` which
12
+ also re-creates project files.
13
+
14
+ ## Fix
15
+
16
+ Add an `allCommand` subcommand to the `start` command so that
17
+ `claude-flow start all` initializes memory, starts the daemon, then runs the
18
+ normal `startAction` (swarm + MCP + health checks).
19
+
20
+ Also adds the subcommand to the `subcommands` array and a corresponding
21
+ example entry.
22
+
23
+ ## Files Patched
24
+
25
+ - `commands/start.js`
26
+
27
+ ## Ops
28
+
29
+ 2 ops in fix.py
@@ -0,0 +1,50 @@
1
+ # SG-005: add 'start all' subcommand to start everything at once
2
+ # GitHub: #1177
3
+
4
+ patch("SG-005a: add allCommand subcommand definition",
5
+ START_CMD,
6
+ """// Quick start subcommand
7
+ const quickCommand = {""",
8
+ """// Start-all subcommand — SG-005
9
+ const allCommand = {
10
+ name: 'all',
11
+ aliases: ['everything'],
12
+ description: 'Start memory, daemon, swarm, and MCP server',
13
+ action: async (ctx) => {
14
+ // Check initialization
15
+ if (!isInitialized(ctx.cwd)) {
16
+ output.printError('Claude Flow is not initialized in this directory');
17
+ output.printInfo('Run "claude-flow init" first, or use "claude-flow start quick"');
18
+ return { success: false, exitCode: 1 };
19
+ }
20
+ output.writeln();
21
+ output.writeln(output.bold('Starting all Claude Flow services'));
22
+ output.writeln();
23
+ const { execSync } = await import('child_process');
24
+ // Step 1: Initialize memory
25
+ try {
26
+ output.writeln(output.dim(' Initializing memory database...'));
27
+ execSync('npx @claude-flow/cli@latest memory init 2>/dev/null', {
28
+ stdio: 'pipe', cwd: ctx.cwd, timeout: 30000
29
+ });
30
+ output.writeln(' \\u2713 Memory initialized');
31
+ } catch { output.writeln(' Memory database already exists'); }
32
+ // Step 2: Start daemon
33
+ try {
34
+ output.writeln(output.dim(' Starting daemon...'));
35
+ execSync('npx @claude-flow/cli@latest daemon start 2>/dev/null &', {
36
+ stdio: 'pipe', cwd: ctx.cwd, timeout: 10000
37
+ });
38
+ output.writeln(' \\u2713 Daemon started');
39
+ } catch { output.writeln(' Daemon may already be running'); }
40
+ // Step 3: Start swarm + MCP via normal startAction
41
+ return startAction(ctx);
42
+ }
43
+ };
44
+ // Quick start subcommand
45
+ const quickCommand = {""")
46
+
47
+ patch("SG-005b: register allCommand in subcommands array and add example",
48
+ START_CMD,
49
+ """ subcommands: [stopCommand, restartCommand, quickCommand],""",
50
+ """ subcommands: [stopCommand, restartCommand, quickCommand, allCommand],""")
@@ -0,0 +1 @@
1
+ grep "allCommand" commands/start.js
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
- // scripts/update-docs.mjs — Regenerate documentation from dynamic patch discovery.
3
- // Updates: README.md, CLAUDE.md, npm/README.md, npm/config.json
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).
4
5
  //
5
- // Usage: node scripts/update-docs.mjs [--check]
6
- // --check Exit 1 if docs are out of date (for CI), don't write.
6
+ // Usage: node scripts/preflight.mjs [--check]
7
+ // --check Exit 1 if anything is out of date (for hooks/CI), don't write.
7
8
 
8
9
  import { readFileSync, writeFileSync } from 'node:fs';
9
10
  import { resolve, dirname } from 'node:path';
@@ -17,6 +18,14 @@ const checkOnly = process.argv.includes('--check');
17
18
  const data = discover();
18
19
  const { patches, categories, stats } = data;
19
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
+
20
29
  // ── Helpers ──
21
30
 
22
31
  /** Group patches by prefix, preserving sort order. */
@@ -57,6 +66,20 @@ function replaceMarkerSection(filePath, markerName, newContent) {
57
66
  return true;
58
67
  }
59
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
+
60
83
  // ── Generate README.md defect index ──
61
84
 
62
85
  function generateReadmeIndex() {
@@ -142,13 +165,21 @@ function updateNpmReadme() {
142
165
  );
143
166
  }
144
167
 
145
- // ── Update npm/config.json counts ──
168
+ // ── Sync npm/config.json (version + defect counts) ──
146
169
 
147
170
  function updateNpmConfig() {
148
171
  const filePath = resolve(ROOT, 'npm', 'config.json');
149
172
  const config = JSON.parse(readFileSync(filePath, 'utf-8'));
150
173
 
151
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
152
183
  if (config.defects?.total !== stats.total) {
153
184
  config.defects.total = stats.total;
154
185
  changed = true;
@@ -168,41 +199,51 @@ function updateNpmConfig() {
168
199
 
169
200
  let anyChanged = false;
170
201
 
171
- const readmeChanged = replaceMarkerSection(
172
- resolve(ROOT, 'README.md'),
173
- 'defect-index',
174
- generateReadmeIndex()
175
- );
176
- if (readmeChanged) {
202
+ function report(changed, label) {
203
+ if (!changed) return;
177
204
  anyChanged = true;
178
- console.log(checkOnly ? 'STALE: README.md' : 'Updated: README.md');
205
+ console.log(checkOnly ? `STALE: ${label}` : `Updated: ${label}`);
179
206
  }
180
207
 
181
- const claudeChanged = replaceMarkerSection(
182
- resolve(ROOT, 'CLAUDE.md'),
183
- 'defect-tables',
184
- generateClaudeTables()
208
+ // 1. Doc tables (marker-based sections)
209
+ report(
210
+ replaceMarkerSection(resolve(ROOT, 'README.md'), 'defect-index', generateReadmeIndex()),
211
+ 'README.md (defect index)'
185
212
  );
186
- if (claudeChanged) {
187
- anyChanged = true;
188
- console.log(checkOnly ? 'STALE: CLAUDE.md' : 'Updated: CLAUDE.md');
189
- }
190
-
191
- const npmReadmeChanged = updateNpmReadme();
192
- if (npmReadmeChanged) {
193
- anyChanged = true;
194
- console.log(checkOnly ? 'STALE: npm/README.md' : 'Updated: npm/README.md');
195
- }
196
-
197
- const npmConfigChanged = updateNpmConfig();
198
- if (npmConfigChanged) {
199
- anyChanged = true;
200
- console.log(checkOnly ? 'STALE: npm/config.json' : 'Updated: npm/config.json');
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
+ }
201
242
  }
202
243
 
203
244
  if (!anyChanged) {
204
- console.log('All docs are up to date.');
245
+ console.log('All files are up to date.');
205
246
  } else if (checkOnly) {
206
- console.log('\nDocs are out of date. Run: npm run update-docs');
247
+ console.log('\nFiles are out of date. Run: npm run preflight');
207
248
  process.exit(1);
208
249
  }