claude-flow 3.7.0-alpha.34 → 3.7.0-alpha.36
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/README.md +10 -2
- package/package.json +6 -3
- package/v3/@claude-flow/cli/README.md +10 -2
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +19 -5
- package/v3/@claude-flow/cli/dist/src/init/settings-generator.js +37 -5
- package/v3/@claude-flow/cli/dist/src/init/statusline-generator.js +50 -17
- package/v3/@claude-flow/cli/dist/src/mcp-server.js +38 -5
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +65 -3
- package/v3/@claude-flow/cli/package.json +1 -1
package/README.md
CHANGED
|
@@ -156,11 +156,17 @@ This adds slash commands and agent definitions only. The Ruflo MCP server is NOT
|
|
|
156
156
|
|
|
157
157
|
### CLI Install
|
|
158
158
|
|
|
159
|
+
**macOS / Linux / WSL / Git-Bash:**
|
|
160
|
+
|
|
159
161
|
```bash
|
|
160
|
-
# One-line install
|
|
162
|
+
# One-line install (POSIX shells only — see Windows note below)
|
|
161
163
|
curl -fsSL https://cdn.jsdelivr.net/gh/ruvnet/ruflo@main/scripts/install.sh | bash
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**All platforms (including native Windows PowerShell / cmd):**
|
|
162
167
|
|
|
163
|
-
|
|
168
|
+
```bash
|
|
169
|
+
# Interactive setup wizard — runs identically on every platform
|
|
164
170
|
npx ruflo@latest init wizard
|
|
165
171
|
|
|
166
172
|
# Quick non-interactive init
|
|
@@ -170,6 +176,8 @@ npx ruflo@latest init wizard
|
|
|
170
176
|
npm install -g ruflo@latest
|
|
171
177
|
```
|
|
172
178
|
|
|
179
|
+
> 💡 **Windows users:** the `curl ... | bash` form needs a POSIX shell (Git-Bash, WSL, MSYS). The `npx ruflo@latest init wizard` line works natively in PowerShell and cmd. If you hit an `'bash' is not recognized` error, use the `npx` line instead — both end up running the same init flow.
|
|
180
|
+
|
|
173
181
|
### MCP Server
|
|
174
182
|
|
|
175
183
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "3.7.0-alpha.
|
|
3
|
+
"version": "3.7.0-alpha.36",
|
|
4
4
|
"description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -89,7 +89,10 @@
|
|
|
89
89
|
"make-fetch-happen": ">=15.0.0",
|
|
90
90
|
"express-rate-limit": ">=8.4.1",
|
|
91
91
|
"protobufjs": ">=7.5.5",
|
|
92
|
-
"uuid": ">=14.0.0"
|
|
92
|
+
"uuid": ">=14.0.0",
|
|
93
|
+
"@opentelemetry/core": "1.25.1",
|
|
94
|
+
"@opentelemetry/resources": "1.25.1",
|
|
95
|
+
"@opentelemetry/sdk-trace-base": "1.25.1"
|
|
93
96
|
},
|
|
94
97
|
"devDependencies": {
|
|
95
98
|
"@openai/codex": "^0.98.0",
|
|
@@ -152,4 +155,4 @@
|
|
|
152
155
|
"access": "public",
|
|
153
156
|
"tag": "latest"
|
|
154
157
|
}
|
|
155
|
-
}
|
|
158
|
+
}
|
|
@@ -156,11 +156,17 @@ This adds slash commands and agent definitions only. The Ruflo MCP server is NOT
|
|
|
156
156
|
|
|
157
157
|
### CLI Install
|
|
158
158
|
|
|
159
|
+
**macOS / Linux / WSL / Git-Bash:**
|
|
160
|
+
|
|
159
161
|
```bash
|
|
160
|
-
# One-line install
|
|
162
|
+
# One-line install (POSIX shells only — see Windows note below)
|
|
161
163
|
curl -fsSL https://cdn.jsdelivr.net/gh/ruvnet/ruflo@main/scripts/install.sh | bash
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**All platforms (including native Windows PowerShell / cmd):**
|
|
162
167
|
|
|
163
|
-
|
|
168
|
+
```bash
|
|
169
|
+
# Interactive setup wizard — runs identically on every platform
|
|
164
170
|
npx ruflo@latest init wizard
|
|
165
171
|
|
|
166
172
|
# Quick non-interactive init
|
|
@@ -170,6 +176,8 @@ npx ruflo@latest init wizard
|
|
|
170
176
|
npm install -g ruflo@latest
|
|
171
177
|
```
|
|
172
178
|
|
|
179
|
+
> 💡 **Windows users:** the `curl ... | bash` form needs a POSIX shell (Git-Bash, WSL, MSYS). The `npx ruflo@latest init wizard` line works natively in PowerShell and cmd. If you hit an `'bash' is not recognized` error, use the `npx` line instead — both end up running the same init flow.
|
|
180
|
+
|
|
173
181
|
### MCP Server
|
|
174
182
|
|
|
175
183
|
```bash
|
|
@@ -694,11 +694,25 @@ const statsCommand = {
|
|
|
694
694
|
},
|
|
695
695
|
{
|
|
696
696
|
metric: 'HNSW Index',
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
697
|
+
// ruflo#1989 / #1987: `hnsw.entryCount` is in-process JS state
|
|
698
|
+
// (the live HNSW index of the current Node process). A fresh
|
|
699
|
+
// `memory stats` invocation has never indexed anything, so it
|
|
700
|
+
// reports 0 even when the persistent DB has thousands of
|
|
701
|
+
// entries with embeddings. Use the persistent count from the
|
|
702
|
+
// MCP tool (`entriesWithEmbeddings`, which is the actual
|
|
703
|
+
// count of rows that have a vector) as the source of truth.
|
|
704
|
+
value: (() => {
|
|
705
|
+
const persisted = typeof statsResult.entriesWithEmbeddings === 'number'
|
|
706
|
+
? statsResult.entriesWithEmbeddings
|
|
707
|
+
: null;
|
|
708
|
+
const live = hnsw.entryCount || 0;
|
|
709
|
+
const total = persisted !== null ? Math.max(persisted, live) : live;
|
|
710
|
+
if (!hnsw.available)
|
|
711
|
+
return output.dim('not active');
|
|
712
|
+
if (total === 0)
|
|
713
|
+
return output.warning('available but not initialized');
|
|
714
|
+
return output.success(`active (${total.toLocaleString()} entries)`);
|
|
715
|
+
})(),
|
|
702
716
|
},
|
|
703
717
|
]
|
|
704
718
|
});
|
|
@@ -200,13 +200,45 @@ function generateStatusLineConfig(_options) {
|
|
|
200
200
|
// Claude Code pipes JSON session data to the script via stdin.
|
|
201
201
|
// Valid fields: type, command, padding (optional).
|
|
202
202
|
// The script runs after each assistant message (debounced 300ms).
|
|
203
|
-
// NOTE: statusline must NOT use `cmd /c` — Claude Code manages its stdin
|
|
204
|
-
// directly for statusline commands, and `cmd /c` blocks stdin forwarding.
|
|
205
203
|
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
204
|
+
// ruflo#1948 + #1973: the previous `sh -c 'D="${CLAUDE_PROJECT_DIR:-.}"; …'`
|
|
205
|
+
// form requires a POSIX shell on PATH. On native Windows (no
|
|
206
|
+
// Git-Bash / WSL), `sh` either isn't found or its quoting gets
|
|
207
|
+
// mangled, producing weird artifacts like files named `0)` or
|
|
208
|
+
// `toastr.error('ESD...` from misparsed tokens leaking back into
|
|
209
|
+
// the file system. NEVER use `cmd /c` for statusline — Claude Code
|
|
210
|
+
// manages stdin directly for statusline commands and `cmd /c`
|
|
211
|
+
// blocks the stdin forwarding.
|
|
212
|
+
//
|
|
213
|
+
// Solution: emit a platform-appropriate command at init time.
|
|
214
|
+
// POSIX: `sh -c 'D="…"; … exec node "$D/<script>"'` (existing)
|
|
215
|
+
// Windows: a Node.js one-liner that resolves the path internally
|
|
216
|
+
// using `process.env.CLAUDE_PROJECT_DIR` with a HOME
|
|
217
|
+
// fallback — no shell-quoting hazards because the
|
|
218
|
+
// resolution happens inside node, not in the shell.
|
|
209
219
|
const script = '.claude/helpers/statusline.cjs';
|
|
220
|
+
if (process.platform === 'win32') {
|
|
221
|
+
// The Node CLI's `-e` flag avoids all shell-quoting pitfalls.
|
|
222
|
+
// We write the path resolution in JS:
|
|
223
|
+
// const fs = require('fs'); const p = require('path');
|
|
224
|
+
// const d = process.env.CLAUDE_PROJECT_DIR || '.';
|
|
225
|
+
// const f = p.join(d, '.claude/helpers/statusline.cjs');
|
|
226
|
+
// const home = process.env.USERPROFILE || process.env.HOME || '.';
|
|
227
|
+
// const h = p.join(home, '.claude/helpers/statusline.cjs');
|
|
228
|
+
// require(fs.existsSync(f) ? f : h);
|
|
229
|
+
// …compressed onto one line. Double-quotes around the -e arg are
|
|
230
|
+
// safe on cmd.exe; the inner JS uses single-quotes for strings.
|
|
231
|
+
const js = "const fs=require('fs'),p=require('path');" +
|
|
232
|
+
`const d=process.env.CLAUDE_PROJECT_DIR||'.';` +
|
|
233
|
+
`const f=p.join(d,'${script}');` +
|
|
234
|
+
`const h=p.join(process.env.USERPROFILE||process.env.HOME||'.', '${script}');` +
|
|
235
|
+
'require(fs.existsSync(f)?f:h);';
|
|
236
|
+
return {
|
|
237
|
+
type: 'command',
|
|
238
|
+
command: `node -e "${js}"`,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
// Same project-local / $HOME fallback as `hookCmd()` (see #1943).
|
|
210
242
|
// eslint-disable-next-line no-template-curly-in-string
|
|
211
243
|
const projVar = '${CLAUDE_PROJECT_DIR:-.}';
|
|
212
244
|
// eslint-disable-next-line no-template-curly-in-string
|
|
@@ -225,8 +225,19 @@ function getLearningStats() {
|
|
|
225
225
|
} catch { /* ignore */ }
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
// 3. Count patterns from memory.db using row count (sqlite header bytes 28-31)
|
|
228
|
+
// 3. Count patterns from memory.db using row count (sqlite header bytes 28-31).
|
|
229
|
+
//
|
|
230
|
+
// ruflo#1989: when encryption at rest is enabled, memory.db is no
|
|
231
|
+
// longer a SQLite database -- it is an RFE1-magicked ciphertext blob.
|
|
232
|
+
// The original code blindly read bytes 28-31 as a page count and
|
|
233
|
+
// rendered 3.3B patterns (uint32 of random ciphertext). That
|
|
234
|
+
// cascaded into fake DDD 5/5 / 100% indicators downstream.
|
|
235
|
+
//
|
|
236
|
+
// Guard with the SQLite magic ("SQLite format 3\\0", 16 bytes at
|
|
237
|
+
// offset 0). Also clamp implausible page counts (>1M pages ~= 4GB)
|
|
238
|
+
// to avoid reporting nonsense even on plaintext SQLite.
|
|
229
239
|
if (patterns === 0) {
|
|
240
|
+
const SQLITE_MAGIC = Buffer.from('SQLite format 3\\0', 'binary');
|
|
230
241
|
const memoryPaths = [
|
|
231
242
|
path.join(CWD, '.claude-flow', 'memory.db'),
|
|
232
243
|
path.join(CWD, 'data', 'memory.db'),
|
|
@@ -234,18 +245,27 @@ function getLearningStats() {
|
|
|
234
245
|
];
|
|
235
246
|
for (const dbPath of memoryPaths) {
|
|
236
247
|
try {
|
|
237
|
-
if (fs.existsSync(dbPath))
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
248
|
+
if (!fs.existsSync(dbPath)) continue;
|
|
249
|
+
const fd = fs.openSync(dbPath, 'r');
|
|
250
|
+
const head = Buffer.alloc(16);
|
|
251
|
+
fs.readSync(fd, head, 0, 16, 0);
|
|
252
|
+
if (!head.equals(SQLITE_MAGIC)) {
|
|
253
|
+
// Not plaintext SQLite (likely RFE1 encrypted, an empty
|
|
254
|
+
// file, or some other format). Skip — let the daemon or
|
|
255
|
+
// patterns.json fallback report the real number.
|
|
242
256
|
fs.closeSync(fd);
|
|
243
|
-
|
|
244
|
-
// Each page typically holds ~10-50 rows; use page count as conservative estimate
|
|
245
|
-
// But report 0 if DB exists but has only schema pages (< 3)
|
|
246
|
-
patterns = pageCount > 2 ? pageCount - 2 : 0;
|
|
247
|
-
break;
|
|
257
|
+
continue;
|
|
248
258
|
}
|
|
259
|
+
const buf = Buffer.alloc(4);
|
|
260
|
+
fs.readSync(fd, buf, 0, 4, 28);
|
|
261
|
+
fs.closeSync(fd);
|
|
262
|
+
const pageCount = buf.readUInt32BE(0);
|
|
263
|
+
// Sanity: reject implausible counts (> 1M pages ≈ 4 GB DB).
|
|
264
|
+
if (pageCount > 1_000_000) continue;
|
|
265
|
+
// Each page typically holds ~10-50 rows; use page count as
|
|
266
|
+
// conservative estimate. Report 0 if only schema pages (< 3).
|
|
267
|
+
patterns = pageCount > 2 ? pageCount - 2 : 0;
|
|
268
|
+
break;
|
|
249
269
|
} catch { /* ignore */ }
|
|
250
270
|
}
|
|
251
271
|
}
|
|
@@ -281,17 +301,24 @@ function getV3Progress() {
|
|
|
281
301
|
let domainsCompleted = Math.min(5, Math.floor(dddProgress / 20));
|
|
282
302
|
|
|
283
303
|
// Only derive DDD progress from real ddd-progress.json or real pattern data
|
|
284
|
-
// Don't inflate domains from pattern count — 0 means no DDD work tracked
|
|
285
|
-
|
|
304
|
+
// Don't inflate domains from pattern count — 0 means no DDD work tracked.
|
|
305
|
+
// ruflo#1989: defensively clamp learning.patterns even though
|
|
306
|
+
// getLearningStats already guards against the RFE1-encrypted case --
|
|
307
|
+
// if any future regression in the upstream reader returns a wild
|
|
308
|
+
// value, we do not want to silently inflate DDD to 5/5 / 100%.
|
|
309
|
+
const realPatterns = Number.isFinite(learning.patterns) && learning.patterns >= 0 && learning.patterns < 1_000_000
|
|
310
|
+
? learning.patterns
|
|
311
|
+
: 0;
|
|
312
|
+
if (dddProgress === 0 && realPatterns > 0) {
|
|
286
313
|
// Conservative: only count domains if we have substantial real pattern data
|
|
287
314
|
// Each domain requires ~100 real stored patterns to claim completion
|
|
288
|
-
domainsCompleted = Math.min(5, Math.floor(
|
|
315
|
+
domainsCompleted = Math.min(5, Math.floor(realPatterns / 100));
|
|
289
316
|
dddProgress = Math.floor((domainsCompleted / totalDomains) * 100);
|
|
290
317
|
}
|
|
291
318
|
|
|
292
319
|
return {
|
|
293
320
|
domainsCompleted, totalDomains, dddProgress,
|
|
294
|
-
patternsLearned:
|
|
321
|
+
patternsLearned: realPatterns,
|
|
295
322
|
sessionsCompleted: learning.sessions,
|
|
296
323
|
};
|
|
297
324
|
}
|
|
@@ -377,8 +404,14 @@ function getSystemMetrics() {
|
|
|
377
404
|
if (learningData && learningData.intelligence && learningData.intelligence.score !== undefined) {
|
|
378
405
|
intelligencePct = Math.min(100, Math.floor(learningData.intelligence.score));
|
|
379
406
|
} else {
|
|
380
|
-
// Use real data only — patterns from actual store, vectors from actual DB
|
|
381
|
-
|
|
407
|
+
// Use real data only — patterns from actual store, vectors from actual DB.
|
|
408
|
+
// ruflo#1989: clamp patterns to a sane upper bound. A multi-billion
|
|
409
|
+
// pattern count from a buggy reader would saturate intelligencePct
|
|
410
|
+
// to 100% and silently lie about progress.
|
|
411
|
+
const realPatterns = Number.isFinite(learning.patterns) && learning.patterns >= 0 && learning.patterns < 1_000_000
|
|
412
|
+
? learning.patterns
|
|
413
|
+
: 0;
|
|
414
|
+
const fromPatterns = realPatterns > 0 ? Math.min(100, Math.floor(realPatterns / 20)) : 0;
|
|
382
415
|
const fromVectors = agentdb.vectorCount > 0 ? Math.min(100, Math.floor(agentdb.vectorCount / 20)) : 0;
|
|
383
416
|
intelligencePct = Math.max(fromPatterns, fromVectors);
|
|
384
417
|
}
|
|
@@ -235,6 +235,39 @@ export class MCPServerManager extends EventEmitter {
|
|
|
235
235
|
* Handles stdin/stdout directly like V2 implementation
|
|
236
236
|
*/
|
|
237
237
|
async startStdioServer() {
|
|
238
|
+
// ruflo#1910 — protect the JSON-RPC stdout from any stray
|
|
239
|
+
// console.log/info/debug emitted by lazily-loaded modules
|
|
240
|
+
// (@ruvector/router, @claude-flow/neural, transformers.js, ONNX,
|
|
241
|
+
// semantic-router init, etc.). Codex closes the MCP transport
|
|
242
|
+
// the moment it sees a non-JSON line on stdout, and one such
|
|
243
|
+
// line during a tool batch bricked the whole session.
|
|
244
|
+
//
|
|
245
|
+
// Strategy: replace console.log/info/debug with stderr writers
|
|
246
|
+
// for the rest of the process. JSON-RPC frames go out via the
|
|
247
|
+
// dedicated `writeFrame()` helper below (process.stdout.write
|
|
248
|
+
// with the original native binding, NOT console.log), so the
|
|
249
|
+
// hijack can't accidentally redirect protocol frames too.
|
|
250
|
+
process.env.MCP_STDIO_MODE = '1';
|
|
251
|
+
const originalLog = console.log; // eslint-disable-line no-console
|
|
252
|
+
console.log = (...args) => process.stderr.write('[stdout→stderr] ' + args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ') + '\n');
|
|
253
|
+
console.info = (...args) => process.stderr.write('[stdout→stderr] ' + args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ') + '\n');
|
|
254
|
+
console.debug = (...args) => process.stderr.write('[stdout→stderr] ' + args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ') + '\n');
|
|
255
|
+
/** Send a single JSON-RPC frame to the real stdout. Use this instead
|
|
256
|
+
* of `console.log` so the hijack above can't redirect protocol frames. */
|
|
257
|
+
const writeFrame = (msg) => {
|
|
258
|
+
process.stdout.write(JSON.stringify(msg) + '\n');
|
|
259
|
+
};
|
|
260
|
+
// Reference originalLog to keep the eslint-disable meaningful — also
|
|
261
|
+
// gives us an escape hatch if a test wants to verify it was replaced.
|
|
262
|
+
void originalLog;
|
|
263
|
+
// Catch fatal errors that would otherwise close the transport
|
|
264
|
+
// mid-batch with no JSON-RPC error returned to the client.
|
|
265
|
+
process.on('uncaughtException', (err) => {
|
|
266
|
+
process.stderr.write(`[mcp-stdio] uncaughtException: ${err.stack || err.message}\n`);
|
|
267
|
+
});
|
|
268
|
+
process.on('unhandledRejection', (reason) => {
|
|
269
|
+
process.stderr.write(`[mcp-stdio] unhandledRejection: ${reason instanceof Error ? reason.stack || reason.message : String(reason)}\n`);
|
|
270
|
+
});
|
|
238
271
|
// Import the tool registry
|
|
239
272
|
const { listMCPTools, callMCPTool, hasTool } = await import('./mcp-client.js');
|
|
240
273
|
const VERSION = '3.0.0';
|
|
@@ -277,7 +310,7 @@ export class MCPServerManager extends EventEmitter {
|
|
|
277
310
|
version: VERSION,
|
|
278
311
|
}));
|
|
279
312
|
// Send server initialization notification
|
|
280
|
-
|
|
313
|
+
writeFrame({
|
|
281
314
|
jsonrpc: '2.0',
|
|
282
315
|
method: 'server.initialized',
|
|
283
316
|
params: {
|
|
@@ -290,7 +323,7 @@ export class MCPServerManager extends EventEmitter {
|
|
|
290
323
|
},
|
|
291
324
|
},
|
|
292
325
|
},
|
|
293
|
-
})
|
|
326
|
+
});
|
|
294
327
|
// Handle stdin messages (S-5: bounded buffer to prevent OOM)
|
|
295
328
|
const MAX_BUFFER_SIZE = 10 * 1024 * 1024; // 10MB
|
|
296
329
|
let buffer = '';
|
|
@@ -299,10 +332,10 @@ export class MCPServerManager extends EventEmitter {
|
|
|
299
332
|
if (buffer.length > MAX_BUFFER_SIZE) {
|
|
300
333
|
console.error(`[${new Date().toISOString()}] ERROR [claude-flow-mcp] Buffer exceeded ${MAX_BUFFER_SIZE} bytes, rejecting`);
|
|
301
334
|
buffer = '';
|
|
302
|
-
|
|
335
|
+
writeFrame({
|
|
303
336
|
jsonrpc: '2.0',
|
|
304
337
|
error: { code: -32600, message: 'Request too large' },
|
|
305
|
-
})
|
|
338
|
+
});
|
|
306
339
|
return;
|
|
307
340
|
}
|
|
308
341
|
// Process complete JSON messages
|
|
@@ -314,7 +347,7 @@ export class MCPServerManager extends EventEmitter {
|
|
|
314
347
|
const message = JSON.parse(line);
|
|
315
348
|
const response = await this.handleMCPMessage(message, sessionId);
|
|
316
349
|
if (response) {
|
|
317
|
-
|
|
350
|
+
writeFrame(response);
|
|
318
351
|
}
|
|
319
352
|
}
|
|
320
353
|
catch (error) {
|
|
@@ -70,6 +70,37 @@ function sanitizeMemoryKey(key) {
|
|
|
70
70
|
const safe = key.replace(DANGEROUS_KEY_CHARS, '_');
|
|
71
71
|
return safe.length > MAX_KEY_LENGTH ? safe.slice(0, MAX_KEY_LENGTH) : safe;
|
|
72
72
|
}
|
|
73
|
+
// #1937 — minimal glob → RegExp helper for memory_import_claude exclusion
|
|
74
|
+
// patterns. Anchored. Supports the three operators the issue's voice-fidelity
|
|
75
|
+
// workflow needs:
|
|
76
|
+
// `**` — any chars including path separators
|
|
77
|
+
// `*` — any chars except path separators
|
|
78
|
+
// `?` — exactly one char except a path separator
|
|
79
|
+
// Everything else is regex-escaped. Used to match absolute file paths.
|
|
80
|
+
function globToRegex(pattern) {
|
|
81
|
+
// Tokenize so we can replace `**` before `*` without overlap.
|
|
82
|
+
let out = '';
|
|
83
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
84
|
+
const c = pattern[i];
|
|
85
|
+
if (c === '*' && pattern[i + 1] === '*') {
|
|
86
|
+
out += '.*';
|
|
87
|
+
i++;
|
|
88
|
+
}
|
|
89
|
+
else if (c === '*') {
|
|
90
|
+
out += '[^/\\\\]*';
|
|
91
|
+
}
|
|
92
|
+
else if (c === '?') {
|
|
93
|
+
out += '[^/\\\\]';
|
|
94
|
+
}
|
|
95
|
+
else if (/[.+^$|(){}\[\]\\]/.test(c)) {
|
|
96
|
+
out += '\\' + c;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
out += c;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return new RegExp('^' + out + '$');
|
|
103
|
+
}
|
|
73
104
|
// #1883 — resolve the Claude-Code project memory directory for the *current*
|
|
74
105
|
// project. Claude Code hashes the project path differently per host OS, and
|
|
75
106
|
// our previous logic only POSIX-slash-replaced cwd, which breaks for:
|
|
@@ -677,7 +708,7 @@ export const memoryTools = [
|
|
|
677
708
|
// ===== Claude Code Memory Bridge Tools =====
|
|
678
709
|
{
|
|
679
710
|
name: 'memory_import_claude',
|
|
680
|
-
description: 'Import Claude Code auto-memory files into AgentDB with ONNX vector embeddings. Reads ~/.claude/projects/*/memory/*.md files, parses YAML frontmatter, splits into sections, and stores with 384-dim embeddings for semantic search. Use allProjects=true to import from ALL Claude projects. Pass projectPath to override cwd-based detection (#1883 — required when Ruflo runs in WSL but Claude Code is on Windows). Use when native Read/Write is wrong because you need (a) cross-session retrieval by semantic similarity (vector embeddings) not by file path, (b) namespacing across projects without managing directory layout, or (c) the .swarm/memory.db audit trail. For one-shot file I/O, native Read/Write is fine.',
|
|
711
|
+
description: 'Import Claude Code auto-memory files into AgentDB with ONNX vector embeddings. Reads ~/.claude/projects/*/memory/*.md files, parses YAML frontmatter, splits into sections, and stores with 384-dim embeddings for semantic search. Use allProjects=true to import from ALL Claude projects. Pass projectPath to override cwd-based detection (#1883 — required when Ruflo runs in WSL but Claude Code is on Windows). Pass excludeFilePatterns (glob list) or excludeFiles (absolute path list) to skip voice-load-bearing, PII, or persona-restricted files (#1937). Use when native Read/Write is wrong because you need (a) cross-session retrieval by semantic similarity (vector embeddings) not by file path, (b) namespacing across projects without managing directory layout, or (c) the .swarm/memory.db audit trail. For one-shot file I/O, native Read/Write is fine.',
|
|
681
712
|
category: 'memory',
|
|
682
713
|
inputSchema: {
|
|
683
714
|
type: 'object',
|
|
@@ -685,6 +716,16 @@ export const memoryTools = [
|
|
|
685
716
|
allProjects: { type: 'boolean', description: 'Import from all Claude projects (default: current project only)' },
|
|
686
717
|
namespace: { type: 'string', description: 'Target namespace (default: "claude-memories")' },
|
|
687
718
|
projectPath: { type: 'string', description: '#1883 — explicit project path to hash, used when cwd does not match Claude Code\'s view (e.g. WSL bridge to Windows host). Pass the canonical project root as Claude Code sees it.' },
|
|
719
|
+
excludeFilePatterns: {
|
|
720
|
+
type: 'array',
|
|
721
|
+
items: { type: 'string' },
|
|
722
|
+
description: '#1937 — glob patterns matched against the absolute file path. Files matching ANY pattern are skipped. Supports `*` (any chars within a path segment), `**` (any chars including separators), and `?` (single char). Examples: `**/voice-*.md`, `**/persona-*.md`. Combine with excludeFiles for explicit paths.',
|
|
723
|
+
},
|
|
724
|
+
excludeFiles: {
|
|
725
|
+
type: 'array',
|
|
726
|
+
items: { type: 'string' },
|
|
727
|
+
description: '#1937 — absolute file paths to skip verbatim. Faster than a pattern when the list is known ahead of time (operator captured baselines). Combine with excludeFilePatterns.',
|
|
728
|
+
},
|
|
688
729
|
},
|
|
689
730
|
},
|
|
690
731
|
handler: async (input) => {
|
|
@@ -699,8 +740,18 @@ export const memoryTools = [
|
|
|
699
740
|
const allProjects = input.allProjects;
|
|
700
741
|
const projectPathOverride = input.projectPath;
|
|
701
742
|
const claudeProjectsDir = join(homedir(), '.claude', 'projects');
|
|
743
|
+
// #1937 — voice-fidelity / persona-restricted exclusion.
|
|
744
|
+
const excludeFilePatterns = Array.isArray(input.excludeFilePatterns) ? input.excludeFilePatterns : [];
|
|
745
|
+
const excludeFilesList = Array.isArray(input.excludeFiles) ? new Set(input.excludeFiles) : new Set();
|
|
746
|
+
const excludeRegexes = excludeFilePatterns.map(globToRegex);
|
|
747
|
+
const isExcluded = (absPath) => {
|
|
748
|
+
if (excludeFilesList.has(absPath))
|
|
749
|
+
return true;
|
|
750
|
+
return excludeRegexes.some(re => re.test(absPath));
|
|
751
|
+
};
|
|
702
752
|
// Find memory files
|
|
703
753
|
const memoryFiles = [];
|
|
754
|
+
let excludedByPattern = 0;
|
|
704
755
|
if (allProjects) {
|
|
705
756
|
// Scan all projects
|
|
706
757
|
if (existsSync(claudeProjectsDir)) {
|
|
@@ -712,7 +763,12 @@ export const memoryTools = [
|
|
|
712
763
|
if (!existsSync(memDir))
|
|
713
764
|
continue;
|
|
714
765
|
for (const file of readdirSync(memDir).filter((f) => f.endsWith('.md'))) {
|
|
715
|
-
|
|
766
|
+
const absPath = join(memDir, file);
|
|
767
|
+
if (isExcluded(absPath)) {
|
|
768
|
+
excludedByPattern++;
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
memoryFiles.push({ path: absPath, project: project.name, file });
|
|
716
772
|
}
|
|
717
773
|
}
|
|
718
774
|
}
|
|
@@ -726,7 +782,12 @@ export const memoryTools = [
|
|
|
726
782
|
if (resolved) {
|
|
727
783
|
try {
|
|
728
784
|
for (const file of readdirSync(resolved.memDir).filter((f) => f.endsWith('.md'))) {
|
|
729
|
-
|
|
785
|
+
const absPath = join(resolved.memDir, file);
|
|
786
|
+
if (isExcluded(absPath)) {
|
|
787
|
+
excludedByPattern++;
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
memoryFiles.push({ path: absPath, project: resolved.projectHash, file });
|
|
730
791
|
}
|
|
731
792
|
}
|
|
732
793
|
catch { /* scan error */ }
|
|
@@ -802,6 +863,7 @@ export const memoryTools = [
|
|
|
802
863
|
imported,
|
|
803
864
|
skipped,
|
|
804
865
|
duplicatesSkipped,
|
|
866
|
+
excludedByPattern,
|
|
805
867
|
files: memoryFiles.length,
|
|
806
868
|
projects: projects.size,
|
|
807
869
|
namespace: ns,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.7.0-alpha.
|
|
3
|
+
"version": "3.7.0-alpha.36",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|