@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
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# GV-001: HNSW ghost vectors persist after memory delete
|
|
2
|
+
**Severity**: Medium
|
|
3
|
+
**GitHub**: [#1122](https://github.com/ruvnet/claude-flow/issues/1122)
|
|
4
|
+
## Root Cause
|
|
5
|
+
`deleteEntry()` soft-deletes the SQLite row but never removes the vector from the in-memory HNSW index or its persisted metadata. The search code (`searchHNSWIndex`) iterates HNSW results and looks up each ID in `hnswIndex.entries` Map — ghost vectors match but return stale metadata (key, namespace, content) because the Map entry was never removed.
|
|
6
|
+
## Fix
|
|
7
|
+
After the SQLite soft-delete, remove the entry from `hnswIndex.entries` Map and save updated metadata. The HNSW vector DB (`@ruvector/core`) doesn't support point removal, but the search code already skips entries missing from the Map (`if (!entry) continue`), so removing from the Map is sufficient to suppress ghost results.
|
|
8
|
+
## Files Patched
|
|
9
|
+
- memory/memory-initializer.js
|
|
10
|
+
## Ops
|
|
11
|
+
1 op in fix.py
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# GV-001: Remove HNSW ghost vectors on memory delete
|
|
2
|
+
# GitHub: #1122
|
|
3
|
+
# After SQLite soft-delete, remove entry from persisted HNSW metadata file
|
|
4
|
+
# and in-memory map. Each CLI invocation is a fresh process so hnswIndex is
|
|
5
|
+
# usually null — the file-based cleanup is the primary path.
|
|
6
|
+
# 1 op
|
|
7
|
+
|
|
8
|
+
patch("GV-001: remove HNSW entry on delete",
|
|
9
|
+
MI,
|
|
10
|
+
""" // Get remaining count
|
|
11
|
+
const countResult = db.exec(`SELECT COUNT(*) FROM memory_entries WHERE status = 'active'`);
|
|
12
|
+
const remainingEntries = countResult[0]?.values?.[0]?.[0] || 0;
|
|
13
|
+
// Save updated database""",
|
|
14
|
+
""" // Remove ghost vector from HNSW metadata file
|
|
15
|
+
const entryId = String(checkResult[0].values[0][0]);
|
|
16
|
+
try {
|
|
17
|
+
const swarmDir = path.join(process.cwd(), '.swarm');
|
|
18
|
+
const metadataPath = path.join(swarmDir, 'hnsw.metadata.json');
|
|
19
|
+
if (fs.existsSync(metadataPath)) {
|
|
20
|
+
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
|
|
21
|
+
const filtered = metadata.filter(([id]) => id !== entryId);
|
|
22
|
+
if (filtered.length < metadata.length) {
|
|
23
|
+
fs.writeFileSync(metadataPath, JSON.stringify(filtered));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
} catch { /* best-effort */ }
|
|
27
|
+
// Also clear in-memory index if loaded
|
|
28
|
+
if (hnswIndex?.entries?.has(entryId)) {
|
|
29
|
+
hnswIndex.entries.delete(entryId);
|
|
30
|
+
}
|
|
31
|
+
// Get remaining count
|
|
32
|
+
const countResult = db.exec(`SELECT COUNT(*) FROM memory_entries WHERE status = 'active'`);
|
|
33
|
+
const remainingEntries = countResult[0]?.values?.[0]?.[0] || 0;
|
|
34
|
+
// Save updated database""")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
grep "hnswIndex.entries.delete" memory/memory-initializer.js
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# HK-001: post-edit hook records file_path as "unknown"
|
|
2
|
+
|
|
3
|
+
**Severity**: Medium
|
|
4
|
+
**GitHub**: [#1155](https://github.com/ruvnet/claude-flow/issues/1155)
|
|
5
|
+
|
|
6
|
+
## Root Cause
|
|
7
|
+
|
|
8
|
+
The `hook-handler.cjs` template (in `helpers-generator.js`) reads the edited file
|
|
9
|
+
path from `process.env.TOOL_INPUT_file_path`. However, Claude Code's PostToolUse
|
|
10
|
+
hooks do **not** set individual `TOOL_INPUT_*` environment variables. Instead, tool
|
|
11
|
+
input is delivered via **stdin** as a JSON object:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"tool_name": "Edit",
|
|
16
|
+
"tool_input": { "file_path": "/path/to/file", ... },
|
|
17
|
+
"tool_response": { ... },
|
|
18
|
+
...
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Because the env var is always empty, `recordEdit()` in `intelligence.cjs` logs
|
|
23
|
+
every edit as `file: "unknown"`. The data is consumed by `consolidate()` at session
|
|
24
|
+
end for edit-count analytics — cosmetic but wrong.
|
|
25
|
+
|
|
26
|
+
## Fix
|
|
27
|
+
|
|
28
|
+
Two changes in `init/helpers-generator.js`:
|
|
29
|
+
|
|
30
|
+
1. **Add stdin parsing** after the `argv` line — read and parse the JSON that
|
|
31
|
+
Claude Code pipes to PostToolUse hook commands.
|
|
32
|
+
2. **Update post-edit handler** — read `stdinData.tool_input.file_path` instead
|
|
33
|
+
of `process.env.TOOL_INPUT_file_path`.
|
|
34
|
+
|
|
35
|
+
The `prompt` fallback for the `route` handler is also updated to prefer
|
|
36
|
+
`stdinData.tool_input.command` over the env var.
|
|
37
|
+
|
|
38
|
+
## Files Patched
|
|
39
|
+
|
|
40
|
+
- init/helpers-generator.js
|
|
41
|
+
|
|
42
|
+
## Ops
|
|
43
|
+
|
|
44
|
+
2 ops in fix.py
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# HK-001: post-edit hook records file_path as "unknown"
|
|
2
|
+
# Claude Code passes tool input via stdin JSON, not TOOL_INPUT_* env vars.
|
|
3
|
+
# 3 ops: add stdin parsing, fix prompt fallback, fix post-edit file extraction.
|
|
4
|
+
|
|
5
|
+
patch("HK-001a: add stdin parsing",
|
|
6
|
+
HELPERS_GEN,
|
|
7
|
+
"""'const [,, command, ...args] = process.argv;',
|
|
8
|
+
"const prompt = process.env.PROMPT || process.env.TOOL_INPUT_command || args.join(' ') || '';",""",
|
|
9
|
+
"""'const [,, command, ...args] = process.argv;',
|
|
10
|
+
'',
|
|
11
|
+
'// Read stdin JSON from Claude Code hooks (provides tool_input, tool_name, etc.)',
|
|
12
|
+
'let stdinData = {};',
|
|
13
|
+
'try {',
|
|
14
|
+
" const raw = require(\\'fs\\').readFileSync(0, \\'utf-8\\').trim();",
|
|
15
|
+
" if (raw) stdinData = JSON.parse(raw);",
|
|
16
|
+
'} catch (e) { /* stdin may be empty or non-JSON */ }',
|
|
17
|
+
'',
|
|
18
|
+
"const prompt = process.env.PROMPT || (stdinData.tool_input && stdinData.tool_input.command) || args.join(' ') || '';",""")
|
|
19
|
+
|
|
20
|
+
patch("HK-001b: post-edit read file_path from stdin",
|
|
21
|
+
HELPERS_GEN,
|
|
22
|
+
"""" var file = process.env.TOOL_INPUT_file_path || args[0] || '';",""",
|
|
23
|
+
"""" var file = (stdinData.tool_input && stdinData.tool_input.file_path) || args[0] || '';",""")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
grep "stdinData" init/helpers-generator.js
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# HK-002: MCP hook handlers are stubs that don't persist data
|
|
2
|
+
|
|
3
|
+
**Severity**: High
|
|
4
|
+
**GitHub**: [#1058](https://github.com/ruvnet/claude-flow/issues/1058)
|
|
5
|
+
|
|
6
|
+
## Root Cause
|
|
7
|
+
|
|
8
|
+
Three MCP hook handlers in `hooks-tools.js` return success responses but **never persist any data**:
|
|
9
|
+
|
|
10
|
+
1. **`hooksPostEdit`** (line 512) -- Returns `{recorded: true}` but has no database INSERT
|
|
11
|
+
2. **`hooksPostCommand`** (line 568) -- Same, claims recorded but stores nothing
|
|
12
|
+
3. **`hooksPostTask`** (line 886) -- Returns fake random duration and hardcoded pattern counts
|
|
13
|
+
|
|
14
|
+
The store function `getRealStoreFunction()` (line 23) already exists in the file and is used correctly by other handlers (`hooks_intelligence_trajectory-end`, `hooks_intelligence_pattern-store`). These three just never call it.
|
|
15
|
+
|
|
16
|
+
## Fix
|
|
17
|
+
|
|
18
|
+
Patch each handler to call `getRealStoreFunction()` and persist to appropriate namespaces:
|
|
19
|
+
- `hooksPostEdit` -> namespace: `edits`
|
|
20
|
+
- `hooksPostCommand` -> namespace: `commands`
|
|
21
|
+
- `hooksPostTask` -> namespace: `tasks`
|
|
22
|
+
|
|
23
|
+
## Impact Without Patch
|
|
24
|
+
|
|
25
|
+
- Edit patterns never stored -- can't learn from file edits
|
|
26
|
+
- Command history lost -- can't learn from command outcomes
|
|
27
|
+
- Task outcomes not tracked -- SONA learning has no data
|
|
28
|
+
- Misleading metrics -- statusline shows fake pattern counts
|
|
29
|
+
|
|
30
|
+
## Files Patched
|
|
31
|
+
|
|
32
|
+
- `mcp-tools/hooks-tools.js`
|
|
33
|
+
|
|
34
|
+
## Ops
|
|
35
|
+
|
|
36
|
+
3 ops in fix.py
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# HK-002: MCP hook handlers are stubs that don't persist data
|
|
2
|
+
# GitHub: #1058
|
|
3
|
+
# Restored from deleted HK-001 (commit 95a6a23)
|
|
4
|
+
|
|
5
|
+
# HK-002a: hooksPostEdit - add persistence
|
|
6
|
+
patch("HK-002a: hooksPostEdit persistence",
|
|
7
|
+
MCP_HOOKS,
|
|
8
|
+
""" handler: async (params) => {
|
|
9
|
+
const filePath = params.filePath;
|
|
10
|
+
const success = params.success !== false;
|
|
11
|
+
return {
|
|
12
|
+
recorded: true,
|
|
13
|
+
filePath,
|
|
14
|
+
success,
|
|
15
|
+
timestamp: new Date().toISOString(),
|
|
16
|
+
learningUpdate: success ? 'pattern_reinforced' : 'pattern_adjusted',
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
export const hooksPreCommand""",
|
|
21
|
+
""" handler: async (params) => {
|
|
22
|
+
const filePath = params.filePath;
|
|
23
|
+
const success = params.success !== false;
|
|
24
|
+
const agent = params.agent || 'unknown';
|
|
25
|
+
const timestamp = new Date().toISOString();
|
|
26
|
+
const editId = `edit-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
27
|
+
// HK-002a: Actually persist the edit record
|
|
28
|
+
const storeFn = await getRealStoreFunction();
|
|
29
|
+
let storeResult = { success: false };
|
|
30
|
+
if (storeFn) {
|
|
31
|
+
try {
|
|
32
|
+
storeResult = await storeFn({
|
|
33
|
+
key: editId,
|
|
34
|
+
value: JSON.stringify({ filePath, success, agent, timestamp }),
|
|
35
|
+
namespace: 'edits',
|
|
36
|
+
generateEmbeddingFlag: true,
|
|
37
|
+
tags: [success ? 'success' : 'failure', 'edit', agent],
|
|
38
|
+
});
|
|
39
|
+
} catch (e) { storeResult = { success: false, error: String(e) }; }
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
recorded: storeResult.success,
|
|
43
|
+
filePath,
|
|
44
|
+
success,
|
|
45
|
+
timestamp,
|
|
46
|
+
learningUpdate: success ? 'pattern_reinforced' : 'pattern_adjusted',
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
export const hooksPreCommand""")
|
|
51
|
+
|
|
52
|
+
# HK-002b: hooksPostCommand - add persistence
|
|
53
|
+
patch("HK-002b: hooksPostCommand persistence",
|
|
54
|
+
MCP_HOOKS,
|
|
55
|
+
""" handler: async (params) => {
|
|
56
|
+
const command = params.command;
|
|
57
|
+
const exitCode = params.exitCode || 0;
|
|
58
|
+
return {
|
|
59
|
+
recorded: true,
|
|
60
|
+
command,
|
|
61
|
+
exitCode,
|
|
62
|
+
success: exitCode === 0,
|
|
63
|
+
timestamp: new Date().toISOString(),
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
export const hooksRoute""",
|
|
68
|
+
""" handler: async (params) => {
|
|
69
|
+
const command = params.command;
|
|
70
|
+
const exitCode = params.exitCode || 0;
|
|
71
|
+
const success = exitCode === 0;
|
|
72
|
+
const timestamp = new Date().toISOString();
|
|
73
|
+
const cmdId = `cmd-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
74
|
+
// HK-002b: Actually persist the command record
|
|
75
|
+
const storeFn = await getRealStoreFunction();
|
|
76
|
+
let storeResult = { success: false };
|
|
77
|
+
if (storeFn) {
|
|
78
|
+
try {
|
|
79
|
+
storeResult = await storeFn({
|
|
80
|
+
key: cmdId,
|
|
81
|
+
value: JSON.stringify({ command, exitCode, success, timestamp }),
|
|
82
|
+
namespace: 'commands',
|
|
83
|
+
generateEmbeddingFlag: true,
|
|
84
|
+
tags: [success ? 'success' : 'failure', 'command'],
|
|
85
|
+
});
|
|
86
|
+
} catch (e) { storeResult = { success: false, error: String(e) }; }
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
recorded: storeResult.success,
|
|
90
|
+
command,
|
|
91
|
+
exitCode,
|
|
92
|
+
success,
|
|
93
|
+
timestamp,
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
export const hooksRoute""")
|
|
98
|
+
|
|
99
|
+
# HK-002c: hooksPostTask - add persistence, remove fake random data
|
|
100
|
+
patch("HK-002c: hooksPostTask persistence",
|
|
101
|
+
MCP_HOOKS,
|
|
102
|
+
""" handler: async (params) => {
|
|
103
|
+
const taskId = params.taskId;
|
|
104
|
+
const success = params.success !== false;
|
|
105
|
+
const quality = params.quality || (success ? 0.85 : 0.3);
|
|
106
|
+
return {
|
|
107
|
+
taskId,
|
|
108
|
+
success,
|
|
109
|
+
duration: Math.floor(Math.random() * 300) + 60, // 1-6 minutes in seconds
|
|
110
|
+
learningUpdates: {
|
|
111
|
+
patternsUpdated: success ? 2 : 1,
|
|
112
|
+
newPatterns: success ? 1 : 0,
|
|
113
|
+
trajectoryId: `traj-${Date.now()}`,
|
|
114
|
+
},
|
|
115
|
+
quality,
|
|
116
|
+
timestamp: new Date().toISOString(),
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
// Explain hook""",
|
|
121
|
+
""" handler: async (params) => {
|
|
122
|
+
const taskId = params.taskId;
|
|
123
|
+
const success = params.success !== false;
|
|
124
|
+
const agent = params.agent || 'unknown';
|
|
125
|
+
const quality = params.quality || (success ? 0.85 : 0.3);
|
|
126
|
+
const timestamp = new Date().toISOString();
|
|
127
|
+
// HK-002c: Actually persist the task record
|
|
128
|
+
const storeFn = await getRealStoreFunction();
|
|
129
|
+
let storeResult = { success: false };
|
|
130
|
+
if (storeFn) {
|
|
131
|
+
try {
|
|
132
|
+
storeResult = await storeFn({
|
|
133
|
+
key: `task-${taskId}`,
|
|
134
|
+
value: JSON.stringify({ taskId, success, agent, quality, timestamp }),
|
|
135
|
+
namespace: 'tasks',
|
|
136
|
+
generateEmbeddingFlag: true,
|
|
137
|
+
tags: [success ? 'success' : 'failure', 'task', agent],
|
|
138
|
+
});
|
|
139
|
+
} catch (e) { storeResult = { success: false, error: String(e) }; }
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
taskId,
|
|
143
|
+
success,
|
|
144
|
+
recorded: storeResult.success,
|
|
145
|
+
learningUpdates: {
|
|
146
|
+
patternsUpdated: storeResult.success ? 1 : 0,
|
|
147
|
+
newPatterns: storeResult.success ? 1 : 0,
|
|
148
|
+
trajectoryId: `task-${taskId}`,
|
|
149
|
+
},
|
|
150
|
+
quality,
|
|
151
|
+
timestamp,
|
|
152
|
+
};
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
// Explain hook""")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
grep "HK-002a" mcp-tools/hooks-tools.js
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# HK-003: hooks_metrics MCP handler returns hardcoded fake data
|
|
2
|
+
|
|
3
|
+
**Severity**: High
|
|
4
|
+
**GitHub**: [#1158](https://github.com/ruvnet/claude-flow/issues/1158)
|
|
5
|
+
|
|
6
|
+
## Root Cause
|
|
7
|
+
|
|
8
|
+
The `hooksMetrics` handler in `mcp-tools/hooks-tools.js` returns a static
|
|
9
|
+
object literal with fake values (15 patterns, 87% routing accuracy, 128
|
|
10
|
+
commands executed). It never reads from any persistence layer.
|
|
11
|
+
|
|
12
|
+
This is the same class of defect as HK-002 (hook handlers returning fake
|
|
13
|
+
data without persisting), but for the metrics/dashboard endpoint.
|
|
14
|
+
|
|
15
|
+
## Fix
|
|
16
|
+
|
|
17
|
+
Replace the hardcoded return with a function that:
|
|
18
|
+
1. Reads `.swarm/sona-patterns.json` for pattern counts, confidence, and routing stats
|
|
19
|
+
2. Reads `.ruvector/intelligence.json` for trajectory/command counts and success rates
|
|
20
|
+
3. Computes actual metrics from the persisted data
|
|
21
|
+
4. Falls back to zeros when files don't exist
|
|
22
|
+
5. Preserves the static performance targets (those are design goals, not metrics)
|
|
23
|
+
|
|
24
|
+
## Files Patched
|
|
25
|
+
|
|
26
|
+
- `mcp-tools/hooks-tools.js`
|
|
27
|
+
|
|
28
|
+
## Ops
|
|
29
|
+
|
|
30
|
+
1 op in fix.py
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# HK-003: hooks_metrics MCP handler returns hardcoded fake data
|
|
2
|
+
# GitHub: #1158
|
|
3
|
+
|
|
4
|
+
patch("HK-003a: replace hardcoded metrics with real data reader",
|
|
5
|
+
MCP_HOOKS,
|
|
6
|
+
""" return {
|
|
7
|
+
period,
|
|
8
|
+
patterns: {
|
|
9
|
+
total: 15,
|
|
10
|
+
successful: 12,
|
|
11
|
+
failed: 3,
|
|
12
|
+
avgConfidence: 0.85,
|
|
13
|
+
},
|
|
14
|
+
agents: {
|
|
15
|
+
routingAccuracy: 0.87,
|
|
16
|
+
totalRoutes: 42,
|
|
17
|
+
topAgent: 'coder',
|
|
18
|
+
},
|
|
19
|
+
commands: {
|
|
20
|
+
totalExecuted: 128,
|
|
21
|
+
successRate: 0.94,
|
|
22
|
+
avgRiskScore: 0.15,
|
|
23
|
+
},
|
|
24
|
+
performance: {
|
|
25
|
+
flashAttention: '2.49x-7.47x speedup',
|
|
26
|
+
memoryReduction: '50-75% reduction',
|
|
27
|
+
searchImprovement: '150x-12,500x faster',
|
|
28
|
+
tokenReduction: '32.3% fewer tokens',
|
|
29
|
+
},
|
|
30
|
+
status: 'healthy',
|
|
31
|
+
lastUpdated: new Date().toISOString(),
|
|
32
|
+
};""",
|
|
33
|
+
""" // HK-003: read real metrics from persisted files instead of hardcoded values
|
|
34
|
+
const cwd = process.cwd();
|
|
35
|
+
let patterns = { total: 0, successful: 0, failed: 0, avgConfidence: 0 };
|
|
36
|
+
let agents = { routingAccuracy: 0, totalRoutes: 0, topAgent: 'none' };
|
|
37
|
+
let commands = { totalExecuted: 0, successRate: 0, avgRiskScore: 0 };
|
|
38
|
+
try {
|
|
39
|
+
const sonaPath = cwd + '/.swarm/sona-patterns.json';
|
|
40
|
+
if (existsSync(sonaPath)) {
|
|
41
|
+
const sona = JSON.parse(readFileSync(sonaPath, 'utf-8'));
|
|
42
|
+
const pats = Object.values(sona.patterns || {});
|
|
43
|
+
const successful = pats.filter(p => p.successCount > 0).length;
|
|
44
|
+
patterns = {
|
|
45
|
+
total: pats.length,
|
|
46
|
+
successful,
|
|
47
|
+
failed: pats.filter(p => p.failureCount > 0).length,
|
|
48
|
+
avgConfidence: pats.length > 0 ? pats.reduce((s, p) => s + (p.confidence || 0), 0) / pats.length : 0,
|
|
49
|
+
};
|
|
50
|
+
agents = {
|
|
51
|
+
routingAccuracy: (sona.stats || {}).successfulRoutings > 0 ? sona.stats.successfulRoutings / ((sona.stats.successfulRoutings || 0) + (sona.stats.failedRoutings || 0)) : 0,
|
|
52
|
+
totalRoutes: ((sona.stats || {}).successfulRoutings || 0) + ((sona.stats || {}).failedRoutings || 0),
|
|
53
|
+
topAgent: pats.length > 0 ? pats.sort((a, b) => (b.successCount || 0) - (a.successCount || 0))[0].agent || 'none' : 'none',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
} catch {}
|
|
57
|
+
try {
|
|
58
|
+
const rvPath = cwd + '/.ruvector/intelligence.json';
|
|
59
|
+
if (existsSync(rvPath)) {
|
|
60
|
+
const rv = JSON.parse(readFileSync(rvPath, 'utf-8'));
|
|
61
|
+
const s = rv.stats || {};
|
|
62
|
+
commands = {
|
|
63
|
+
totalExecuted: (s.session_count || 0) + (rv.trajectories || []).length,
|
|
64
|
+
successRate: (rv.trajectories || []).length > 0 ? (rv.trajectories || []).filter(t => t.success).length / (rv.trajectories || []).length : 0,
|
|
65
|
+
avgRiskScore: 0.15,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
} catch {}
|
|
69
|
+
return {
|
|
70
|
+
period,
|
|
71
|
+
patterns,
|
|
72
|
+
agents,
|
|
73
|
+
commands,
|
|
74
|
+
performance: {
|
|
75
|
+
flashAttention: '2.49x-7.47x speedup',
|
|
76
|
+
memoryReduction: '50-75% reduction',
|
|
77
|
+
searchImprovement: '150x-12,500x faster',
|
|
78
|
+
tokenReduction: '32.3% fewer tokens',
|
|
79
|
+
},
|
|
80
|
+
status: 'healthy',
|
|
81
|
+
lastUpdated: new Date().toISOString(),
|
|
82
|
+
};""")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
grep "HK-003" mcp-tools/hooks-tools.js
|
|
@@ -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,11 @@
|
|
|
1
|
+
# HW-001: Headless workers hang — stdin pipe never closed
|
|
2
|
+
**Severity**: Critical
|
|
3
|
+
**GitHub**: [#1111](https://github.com/ruvnet/claude-flow/issues/1111)
|
|
4
|
+
## Root Cause
|
|
5
|
+
`claude --print` is spawned with `stdio: ['pipe', 'pipe', 'pipe']`. The prompt is passed as a CLI argument, so stdin is never written to or closed. The child process hangs waiting for stdin EOF.
|
|
6
|
+
## Fix
|
|
7
|
+
Change stdin to `'ignore'` since it's unused.
|
|
8
|
+
## Files Patched
|
|
9
|
+
- services/headless-worker-executor.js
|
|
10
|
+
## Ops
|
|
11
|
+
1 op in fix.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
grep "'ignore', 'pipe', 'pipe'" services/headless-worker-executor.js
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# HW-002: Headless failures silently swallowed as success
|
|
2
|
+
**Severity**: High
|
|
3
|
+
**GitHub**: [#1112](https://github.com/ruvnet/claude-flow/issues/1112)
|
|
4
|
+
## Root Cause
|
|
5
|
+
`executeWorker()` hardcodes `success: true` for any non-throwing return. When headless execution fails (returns `{success: false}`), `runWorkerLogic()` catches the error and falls through to local stubs that fabricate data. Failures are never surfaced.
|
|
6
|
+
## Fix
|
|
7
|
+
Check `result.success` after headless execution. If false, throw with the error message instead of falling through.
|
|
8
|
+
## Files Patched
|
|
9
|
+
- services/worker-daemon.js
|
|
10
|
+
## Ops
|
|
11
|
+
1 op in fix.py
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# HW-002: Headless failures silently swallowed as success
|
|
2
|
+
# GitHub: #1112
|
|
3
|
+
patch("2: honest failures",
|
|
4
|
+
WD,
|
|
5
|
+
""" if (isHeadlessWorker(workerConfig.type) && this.headlessAvailable && this.headlessExecutor) {
|
|
6
|
+
try {
|
|
7
|
+
this.log('info', `Running ${workerConfig.type} in headless mode (Claude Code AI)`);
|
|
8
|
+
const result = await this.headlessExecutor.execute(workerConfig.type);
|
|
9
|
+
return {
|
|
10
|
+
mode: 'headless',
|
|
11
|
+
...result,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
this.log('warn', `Headless execution failed for ${workerConfig.type}, falling back to local mode`);
|
|
16
|
+
this.emit('headless:fallback', {
|
|
17
|
+
type: workerConfig.type,
|
|
18
|
+
error: error instanceof Error ? error.message : String(error),
|
|
19
|
+
});
|
|
20
|
+
// Fall through to local execution
|
|
21
|
+
}
|
|
22
|
+
}""",
|
|
23
|
+
""" if (isHeadlessWorker(workerConfig.type) && this.headlessAvailable && this.headlessExecutor) {
|
|
24
|
+
let result;
|
|
25
|
+
try {
|
|
26
|
+
this.log('info', `Running ${workerConfig.type} in headless mode (Claude Code AI)`);
|
|
27
|
+
result = await this.headlessExecutor.execute(workerConfig.type);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
31
|
+
this.log('warn', `Headless execution threw for ${workerConfig.type}: ${errorMsg}`);
|
|
32
|
+
this.emit('headless:fallback', { type: workerConfig.type, error: errorMsg });
|
|
33
|
+
throw error instanceof Error ? error : new Error(errorMsg);
|
|
34
|
+
}
|
|
35
|
+
if (result.success) {
|
|
36
|
+
return { mode: 'headless', ...result };
|
|
37
|
+
}
|
|
38
|
+
const errorMsg = result.error || 'Unknown headless failure';
|
|
39
|
+
this.log('warn', `Headless failed for ${workerConfig.type}: ${errorMsg}`);
|
|
40
|
+
this.emit('headless:fallback', { type: workerConfig.type, error: errorMsg });
|
|
41
|
+
throw new Error(`Headless execution failed for ${workerConfig.type}: ${errorMsg}`);
|
|
42
|
+
}""")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
grep "result.success" services/worker-daemon.js
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# HW-003: Worker scheduling intervals too aggressive + settings ignored
|
|
2
|
+
**Severity**: High
|
|
3
|
+
**GitHub**: [#1113](https://github.com/ruvnet/claude-flow/issues/1113)
|
|
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. Additionally, `daemon.schedules` from `.claude/settings.json` is never read — user-configured intervals are completely ignored.
|
|
6
|
+
## Fix
|
|
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
|
+
## Files Patched
|
|
9
|
+
- services/worker-daemon.js
|
|
10
|
+
## Ops
|
|
11
|
+
4 ops in fix.py
|