claude-flow 3.56.5 → 3.56.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/.claude/helpers/auto-memory-hook.mjs +109 -10
- package/.claude/helpers/hook-handler.cjs +198 -1
- package/.claude/helpers/intelligence.cjs +57 -4
- package/node_modules/@claude-flow/codex/README.md +20 -3
- package/node_modules/@claude-flow/codex/dist/cli.js +7 -0
- package/node_modules/@claude-flow/codex/dist/cli.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/index.d.ts +3 -3
- package/node_modules/@claude-flow/codex/dist/index.js +1 -1
- package/node_modules/@claude-flow/codex/dist/initializer.d.ts +8 -3
- package/node_modules/@claude-flow/codex/dist/initializer.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/initializer.js +75 -13
- package/node_modules/@claude-flow/codex/dist/initializer.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/types.d.ts +8 -0
- package/node_modules/@claude-flow/codex/dist/types.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/package.json +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/package.json +7 -4
- package/v3/@claude-flow/cli/bin/cli.js +11 -1
- package/v3/@claude-flow/cli/bin/mcp-server.js +11 -1
- package/v3/@claude-flow/cli/dist/src/commands/doctor-native-addon.d.ts +23 -0
- package/v3/@claude-flow/cli/dist/src/commands/doctor-native-addon.js +135 -0
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +3 -0
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +8 -1
- package/v3/@claude-flow/cli/dist/src/commands/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/commands/init.d.ts +2 -0
- package/v3/@claude-flow/cli/dist/src/commands/init.js +37 -5
- package/v3/@claude-flow/cli/dist/src/commands/mods.d.ts +3 -0
- package/v3/@claude-flow/cli/dist/src/commands/mods.js +19 -5
- package/v3/@claude-flow/cli/dist/src/commands/tui.d.ts +12 -0
- package/v3/@claude-flow/cli/dist/src/commands/tui.js +98 -0
- package/v3/@claude-flow/cli/dist/src/fs-secure.d.ts +25 -0
- package/v3/@claude-flow/cli/dist/src/fs-secure.js +51 -2
- package/v3/@claude-flow/cli/dist/src/init/executor.js +11 -14
- package/v3/@claude-flow/cli/dist/src/init/helper-commands.d.ts +2 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-commands.js +42 -11
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +4 -1
- package/v3/@claude-flow/cli/dist/src/init/settings-generator.js +3 -3
- package/v3/@claude-flow/cli/dist/src/init/settings-risk-scanner.js +25 -6
- package/v3/@claude-flow/cli/dist/src/mcp-server.js +10 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/capability-brain.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/capability-brain.js +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +41 -15
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +2 -1
- package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.d.ts +3 -1
- package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.js +3 -1
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +4 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +3 -2
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +4 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +12 -11
- package/v3/@claude-flow/cli/dist/src/mods/claude-installs.js +7 -3
- package/v3/@claude-flow/cli/dist/src/mods/install.js +32 -12
- package/v3/@claude-flow/cli/dist/src/mods/plugin-resolve.js +3 -1
- package/v3/@claude-flow/cli/dist/src/mods/probe.js +92 -4
- package/v3/@claude-flow/cli/dist/src/services/command-recording.d.ts +17 -0
- package/v3/@claude-flow/cli/dist/src/services/command-recording.js +91 -0
- package/v3/@claude-flow/cli/package.json +4 -4
- package/v3/@claude-flow/shared/dist/events/rvf-event-log.js +19 -11
- package/v3/@claude-flow/shared/dist/resilience/circuit-breaker.js +5 -3
- package/v3/@claude-flow/shared/package.json +1 -1
|
@@ -107,18 +107,38 @@ export function installMod(projectRoot, scope, dryRun = false, plugins = MOD_PLU
|
|
|
107
107
|
backup = `${settingsFile}.bak-ruflo-mods-${Date.now()}`;
|
|
108
108
|
copyFileSync(settingsFile, backup);
|
|
109
109
|
}
|
|
110
|
-
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
110
|
+
// One operation: the settings and the record that lets uninstall take them back are written together.
|
|
111
|
+
// If the record cannot be written, the settings go back as they were (or the file ruflo created goes
|
|
112
|
+
// away), so nothing is left half-installed with no record to remove it by.
|
|
113
|
+
try {
|
|
114
|
+
writeJson(settingsFile, next);
|
|
115
|
+
// What ruflo added once is still ruflo's to remove: merge into the record.
|
|
116
|
+
const record = readRecord(projectRoot) ?? { version: 2, installedAt: '', files: {} };
|
|
117
|
+
const before = record.files[settingsFile] ?? { plugins: [], marketplace: false, env: false };
|
|
118
|
+
record.files[settingsFile] = {
|
|
119
|
+
plugins: [...new Set([...before.plugins, ...added.plugins])],
|
|
120
|
+
marketplace: before.marketplace || added.marketplace,
|
|
121
|
+
env: before.env || added.env,
|
|
122
|
+
claudeInstalled: before.claudeInstalled ?? [],
|
|
123
|
+
};
|
|
124
|
+
record.installedAt = new Date().toISOString();
|
|
125
|
+
writeJson(join(resolve(projectRoot), INSTALL_RECORD), record);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
try {
|
|
129
|
+
if (backup) {
|
|
130
|
+
copyFileSync(backup, settingsFile);
|
|
131
|
+
unlinkSync(backup);
|
|
132
|
+
}
|
|
133
|
+
else if (existsSync(settingsFile)) {
|
|
134
|
+
unlinkSync(settingsFile);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// the restore itself failed: the original error below is the one to report
|
|
139
|
+
}
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
122
142
|
return { settingsFile, backup, added, dryRun, next };
|
|
123
143
|
}
|
|
124
144
|
/** The install record, v1 (3.50.0: one file, ruflo-mods only) read as v2. */
|
|
@@ -160,7 +160,9 @@ export function resolveFindings(projectRoot, scope, configDir, fs = nodeReadFs,
|
|
|
160
160
|
else if (!market.location) {
|
|
161
161
|
findings.push(installed.installed
|
|
162
162
|
? { name, status: 'pass', message: `installed (${installed.scope} scope, ${installed.installPath})` }
|
|
163
|
-
:
|
|
163
|
+
: plugin.required
|
|
164
|
+
? { name, status: 'warn', message: 'loads once Claude Code has cloned the marketplace (a headless `claude -p` run on a fresh config does not clone it, so the mod is absent there)', fix }
|
|
165
|
+
: { name, status: 'warn', message: 'pending: not released in the ruflo marketplace yet' });
|
|
164
166
|
}
|
|
165
167
|
else if (!plugin.required) {
|
|
166
168
|
findings.push({ name, status: 'warn', message: `pending: not in the ruflo marketplace yet (${market.location})${also}` });
|
|
@@ -8,8 +8,8 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
8
8
|
import { homedir, platform } from 'node:os';
|
|
9
9
|
import { join, resolve } from 'node:path';
|
|
10
10
|
import { findClaudeInstalls, judgeInstalls } from './claude-installs.js';
|
|
11
|
-
import { ENABLE_ENV, MOD_PLUGIN_ID, MOD_PLUGIN_IDS, readRecord, readSettingsFile, settingsFileFor } from './install.js';
|
|
12
|
-
import { claudeConfigDir, resolveFindings } from './plugin-resolve.js';
|
|
11
|
+
import { ENABLE_ENV, MARKETPLACE_NAME, MOD_PLUGIN_ID, MOD_PLUGIN_IDS, readRecord, readSettingsFile, settingsFileFor } from './install.js';
|
|
12
|
+
import { claudeConfigDir, installedState, resolveFindings } from './plugin-resolve.js';
|
|
13
13
|
import { PROJECTION_RELATIVE } from './policy-projection.js';
|
|
14
14
|
export const HANDSHAKE_MARKER = 'RUFLO_MODS_OWNS';
|
|
15
15
|
export const HEARTBEAT_RELATIVE = join('.claude-flow', 'mods', 'session.json');
|
|
@@ -53,6 +53,65 @@ export function legacyCliHooks(files) {
|
|
|
53
53
|
return commands(LEGACY_EVENTS[sub]).some((c) => word.test(c));
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
+
/** Settings files (in the order given) where `enabledPlugins[id]` is exactly `value`. */
|
|
57
|
+
function enabledValues(files, id) {
|
|
58
|
+
const out = new Map();
|
|
59
|
+
for (const f of files) {
|
|
60
|
+
try {
|
|
61
|
+
const v = get(readSettingsFile(f), 'enabledPlugins', id);
|
|
62
|
+
if (v !== undefined)
|
|
63
|
+
out.set(f, v);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// an unparseable file is reported elsewhere; never overwritten
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
function partialInstallFindings(root, files, _inputs) {
|
|
72
|
+
const enabled = new Set(MOD_PLUGIN_IDS.filter((id) => [...enabledValues(files, id).values()].includes(true)));
|
|
73
|
+
const out = [];
|
|
74
|
+
const parts = [];
|
|
75
|
+
if (enabled.size > 0 && enabled.size < MOD_PLUGIN_IDS.length) {
|
|
76
|
+
const missing = MOD_PLUGIN_IDS.filter((id) => !enabled.has(id) && ![...enabledValues(files, id).values()].includes(false));
|
|
77
|
+
if (missing.length)
|
|
78
|
+
parts.push(`enabled ${[...enabled].join(', ')} but not ${missing.join(', ')}`);
|
|
79
|
+
}
|
|
80
|
+
if (enabled.size > 0) {
|
|
81
|
+
const declared = files.some((f) => {
|
|
82
|
+
try {
|
|
83
|
+
return get(readSettingsFile(f), 'extraKnownMarketplaces', MARKETPLACE_NAME) !== undefined;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
if (!declared)
|
|
90
|
+
parts.push(`no \`${MARKETPLACE_NAME}\` marketplace is declared in settings for Claude Code to load them from`);
|
|
91
|
+
}
|
|
92
|
+
const record = readRecord(root);
|
|
93
|
+
if (record) {
|
|
94
|
+
const lost = [];
|
|
95
|
+
for (const [file, added] of Object.entries(record.files)) {
|
|
96
|
+
try {
|
|
97
|
+
const now = readSettingsFile(file);
|
|
98
|
+
for (const id of added.plugins)
|
|
99
|
+
if (get(now, 'enabledPlugins', id) !== true && get(now, 'enabledPlugins', id) !== false)
|
|
100
|
+
lost.push(id);
|
|
101
|
+
if (added.marketplace && get(now, 'extraKnownMarketplaces', MARKETPLACE_NAME) === undefined)
|
|
102
|
+
lost.push(`${MARKETPLACE_NAME} marketplace`);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// unreadable settings: nothing to compare
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (lost.length)
|
|
109
|
+
parts.push(`the install record says ruflo added ${[...new Set(lost)].join(', ')}, which the settings no longer have`);
|
|
110
|
+
}
|
|
111
|
+
if (parts.length)
|
|
112
|
+
out.push({ name: 'partial install', status: 'warn', message: parts.join('; '), fix: 'ruflo mods install' });
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
56
115
|
export function probeMods(inputs) {
|
|
57
116
|
const root = resolve(inputs.projectRoot);
|
|
58
117
|
const home = inputs.home ?? homedir();
|
|
@@ -69,9 +128,38 @@ export function probeMods(inputs) {
|
|
|
69
128
|
return false;
|
|
70
129
|
}
|
|
71
130
|
});
|
|
131
|
+
// A plugin a person set to false is their decision: `ruflo mods install` never flips it, so it is not the repair.
|
|
132
|
+
const disabledIn = enabledIn ? undefined : files.find((f) => {
|
|
133
|
+
try {
|
|
134
|
+
return get(readSettingsFile(f), 'enabledPlugins', MOD_PLUGIN_ID) === false;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
const cachedInstall = enabledIn || disabledIn ? { installed: false } : installedState(root, configDir, MOD_PLUGIN_ID, inputs.fs);
|
|
141
|
+
const scopeOf = (f) => (f === files[0] ? 'local' : f === files[1] ? 'project' : 'user');
|
|
72
142
|
findings.push(enabledIn
|
|
73
143
|
? { name: 'ruflo-mods plugin', status: 'pass', message: `enabled in settings: ${enabledIn} (whether Claude Code can load it: see below)` }
|
|
74
|
-
:
|
|
144
|
+
: disabledIn
|
|
145
|
+
? {
|
|
146
|
+
name: 'ruflo-mods plugin',
|
|
147
|
+
status: 'warn',
|
|
148
|
+
message: `disabled in ${disabledIn} (enabledPlugins is false): the mod does not load and classic hooks handle every event; \`ruflo mods install\` leaves a plugin you switched off alone`,
|
|
149
|
+
fix: `claude plugin enable ${MOD_PLUGIN_ID} --scope ${scopeOf(disabledIn)}`,
|
|
150
|
+
}
|
|
151
|
+
: cachedInstall.installed
|
|
152
|
+
? {
|
|
153
|
+
name: 'ruflo-mods plugin',
|
|
154
|
+
status: 'warn',
|
|
155
|
+
message: `installed (${cachedInstall.scope} scope) but not enabled in this project's settings: the mod does not load`,
|
|
156
|
+
fix: `claude plugin enable ${MOD_PLUGIN_ID} --scope ${cachedInstall.scope === 'user' ? 'user' : 'local'}`,
|
|
157
|
+
}
|
|
158
|
+
: { name: 'ruflo-mods plugin', status: 'warn', message: 'not enabled; classic hooks handle every event', fix: 'ruflo mods install' });
|
|
159
|
+
// 1b. A half-finished install: some of the mod plugins enabled and not the rest, a plugin enabled with no
|
|
160
|
+
// marketplace declared for Claude Code to load it from, or keys the install record says ruflo added that
|
|
161
|
+
// the settings file no longer has. `ruflo mods install` is idempotent and adds exactly what is missing.
|
|
162
|
+
findings.push(...partialInstallFindings(root, files, inputs));
|
|
75
163
|
// 1a. Enabled is a request; Claude Code silently skips a plugin its
|
|
76
164
|
// marketplace clone does not carry (a clone older than the plugin).
|
|
77
165
|
if (enabledIn) {
|
|
@@ -163,7 +251,7 @@ export function probeMods(inputs) {
|
|
|
163
251
|
const owned = get(beat, 'owned');
|
|
164
252
|
findings.push(typeof startedAt === 'string'
|
|
165
253
|
? { name: 'last mod start', status: 'pass', message: `${startedAt}, owning ${Array.isArray(owned) && owned.length ? owned.join(', ') : 'nothing'}` }
|
|
166
|
-
: { name: 'last mod start', status: enabledIn ? 'warn' : 'pass', message: 'the mod has not started in this project' + (enabledIn ? ' (restart Claude Code; check function hooks above)' : '') });
|
|
254
|
+
: { name: 'last mod start', status: enabledIn ? 'warn' : 'pass', message: 'the mod has not started in this project' + (enabledIn ? ' (restart Claude Code; check function hooks above)' : ''), ...(enabledIn ? { fix: 'restart Claude Code in this project, then run /ruflo mods' } : {}) });
|
|
167
255
|
if (readRecord(root))
|
|
168
256
|
findings.push({ name: 'install record', status: 'pass', message: join(root, '.claude-flow', 'mods', 'install.json') });
|
|
169
257
|
return findings;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Longest command text kept per row. */
|
|
2
|
+
export declare const MAX_RECORDED_COMMAND_CHARS = 2000;
|
|
3
|
+
/**
|
|
4
|
+
* True when command recording is switched off. Any one of these turns it off:
|
|
5
|
+
* - `CLAUDE_FLOW_HOOKS_RECORD_COMMANDS` = 0 | false | off | no
|
|
6
|
+
* - `CLAUDE_FLOW_HOOKS_ENABLED` = 0 | false | off | no
|
|
7
|
+
* - `hooks.recordCommands: false` in `claude-flow.config.json` or `.claude-flow/config.json`
|
|
8
|
+
*/
|
|
9
|
+
export declare function commandRecordingDisabled(env?: Record<string, string | undefined>, cwd?: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* The form of a command that may be written to the store: secrets of the
|
|
12
|
+
* common shapes replaced with `[redacted]`, and the text capped at
|
|
13
|
+
* `MAX_RECORDED_COMMAND_CHARS`. This is best effort, not a guarantee; the off
|
|
14
|
+
* switch is the guarantee.
|
|
15
|
+
*/
|
|
16
|
+
export declare function redactCommandForStorage(command: string): string;
|
|
17
|
+
//# sourceMappingURL=command-recording.d.ts.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy for what the PostToolUse Bash hook may keep (#4056).
|
|
3
|
+
*
|
|
4
|
+
* `hooks post-command` used to store every command in full as
|
|
5
|
+
* `commands/cmd-<ts>`: 27k rows / 285 MB on one project, full command text
|
|
6
|
+
* that can carry tokens, and no way to switch it off. This module answers two
|
|
7
|
+
* questions for that writer:
|
|
8
|
+
*
|
|
9
|
+
* 1. May a command be recorded at all? (`commandRecordingDisabled`)
|
|
10
|
+
* 2. What form of it may be stored? (`redactCommandForStorage`)
|
|
11
|
+
*
|
|
12
|
+
* Every pattern below is bounded, and the input is capped before any pattern
|
|
13
|
+
* runs, so a hostile command cannot stall the hook (the same lesson as the
|
|
14
|
+
* settings-risk scanner, #4053).
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from 'node:fs';
|
|
17
|
+
import * as path from 'node:path';
|
|
18
|
+
/** Longest command text kept per row. */
|
|
19
|
+
export const MAX_RECORDED_COMMAND_CHARS = 2000;
|
|
20
|
+
/** Only this much of a command is ever scanned; the stored text is shorter still. */
|
|
21
|
+
const MAX_SCANNED_CHARS = 8000;
|
|
22
|
+
const OFF_VALUES = new Set(['0', 'false', 'off', 'no']);
|
|
23
|
+
const isOff = (v) => v !== undefined && OFF_VALUES.has(v.trim().toLowerCase());
|
|
24
|
+
function configDisablesRecording(cwd) {
|
|
25
|
+
for (const file of [
|
|
26
|
+
path.resolve(cwd, 'claude-flow.config.json'),
|
|
27
|
+
path.resolve(cwd, '.claude-flow', 'config.json'),
|
|
28
|
+
]) {
|
|
29
|
+
try {
|
|
30
|
+
if (!fs.existsSync(file))
|
|
31
|
+
continue;
|
|
32
|
+
const raw = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
33
|
+
if (raw?.hooks?.recordCommands === false)
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
/* malformed config: keep the default (recording on) rather than guess */
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* True when command recording is switched off. Any one of these turns it off:
|
|
44
|
+
* - `CLAUDE_FLOW_HOOKS_RECORD_COMMANDS` = 0 | false | off | no
|
|
45
|
+
* - `CLAUDE_FLOW_HOOKS_ENABLED` = 0 | false | off | no
|
|
46
|
+
* - `hooks.recordCommands: false` in `claude-flow.config.json` or `.claude-flow/config.json`
|
|
47
|
+
*/
|
|
48
|
+
export function commandRecordingDisabled(env = process.env, cwd = process.cwd()) {
|
|
49
|
+
if (isOff(env.CLAUDE_FLOW_HOOKS_RECORD_COMMANDS))
|
|
50
|
+
return true;
|
|
51
|
+
if (isOff(env.CLAUDE_FLOW_HOOKS_ENABLED))
|
|
52
|
+
return true;
|
|
53
|
+
return configDisablesRecording(cwd);
|
|
54
|
+
}
|
|
55
|
+
const QUOTED_OR_BARE = `"[^"]{0,512}"|'[^']{0,512}'|[^\\s"']{1,512}`;
|
|
56
|
+
// Order matters little; each rule only ever shortens the text.
|
|
57
|
+
const RULES = [
|
|
58
|
+
// Authorization: Bearer xyz / authorization=Basic xyz
|
|
59
|
+
[/(authorization["']?\s*[:=]\s*)(?:(?:bearer|basic|token)\s+)?[^\s"']{1,512}/gi, '$1[redacted]'],
|
|
60
|
+
// --password x / --token=x / --api-key "x" / -token x
|
|
61
|
+
[new RegExp(`(--?(?:password|passwd|pwd|token|secret|api[-_]?key|access[-_]?token|auth[-_]?token|client[-_]?secret)(?:=|\\s+))(?:${QUOTED_OR_BARE})`, 'gi'), '$1[redacted]'],
|
|
62
|
+
// NPM_TOKEN=x AWS_SECRET_ACCESS_KEY=x DB_PASSWORD=x
|
|
63
|
+
[new RegExp(`\\b([A-Za-z_][A-Za-z0-9_]{0,63}(?:TOKEN|SECRET|PASSWORD|PASSWD|API_?KEY|ACCESS_?KEY|PRIVATE_?KEY|CREDENTIALS?))=(?:${QUOTED_OR_BARE})`, 'gi'), '$1=[redacted]'],
|
|
64
|
+
// user:password@host in a URL
|
|
65
|
+
[/(:\/\/[^\s:@/]{1,128}:)[^\s@/]{1,256}(@)/g, '$1[redacted]$2'],
|
|
66
|
+
// well-known token shapes
|
|
67
|
+
[/\b(?:sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|xox[abprs]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{16}|npm_[A-Za-z0-9]{20,}|eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,})/g, '[redacted]'],
|
|
68
|
+
];
|
|
69
|
+
/**
|
|
70
|
+
* The form of a command that may be written to the store: secrets of the
|
|
71
|
+
* common shapes replaced with `[redacted]`, and the text capped at
|
|
72
|
+
* `MAX_RECORDED_COMMAND_CHARS`. This is best effort, not a guarantee; the off
|
|
73
|
+
* switch is the guarantee.
|
|
74
|
+
*/
|
|
75
|
+
export function redactCommandForStorage(command) {
|
|
76
|
+
let text = String(command);
|
|
77
|
+
if (text.length > MAX_SCANNED_CHARS) {
|
|
78
|
+
// Cut at a token boundary so a half-token at the cut is not left behind.
|
|
79
|
+
const cut = text.slice(0, MAX_SCANNED_CHARS);
|
|
80
|
+
const lastSpace = cut.search(/\s\S*$/);
|
|
81
|
+
text = lastSpace > 0 ? cut.slice(0, lastSpace) : cut;
|
|
82
|
+
text += ' ';
|
|
83
|
+
}
|
|
84
|
+
for (const [re, replacement] of RULES)
|
|
85
|
+
text = text.replace(re, replacement);
|
|
86
|
+
if (text.length > MAX_RECORDED_COMMAND_CHARS) {
|
|
87
|
+
text = `${text.slice(0, MAX_RECORDED_COMMAND_CHARS)} …[truncated]`;
|
|
88
|
+
}
|
|
89
|
+
return text.trimEnd();
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=command-recording.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.56.
|
|
3
|
+
"version": "3.56.7",
|
|
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",
|
|
@@ -98,13 +98,13 @@
|
|
|
98
98
|
"vitest": "^4.1.0"
|
|
99
99
|
},
|
|
100
100
|
"dependencies": {
|
|
101
|
-
"@claude-flow/cli-core": "3.7.
|
|
102
|
-
"@claude-flow/codex": "3.0.
|
|
101
|
+
"@claude-flow/cli-core": "3.7.1",
|
|
102
|
+
"@claude-flow/codex": "3.0.4",
|
|
103
103
|
"@claude-flow/mcp": "3.1.1",
|
|
104
104
|
"@claude-flow/neural": "3.0.0",
|
|
105
105
|
"@claude-flow/plugin-agent-federation": "1.0.1",
|
|
106
106
|
"@claude-flow/security": "3.0.4",
|
|
107
|
-
"@claude-flow/shared": "3.0.
|
|
107
|
+
"@claude-flow/shared": "3.0.3",
|
|
108
108
|
"@iarna/toml": "^2.2.5",
|
|
109
109
|
"@noble/ed25519": "2.3.0",
|
|
110
110
|
"@ruvector/rabitq-wasm": "0.1.0",
|
|
@@ -69,6 +69,12 @@ export class RvfEventLog extends EventEmitter {
|
|
|
69
69
|
async initialize() {
|
|
70
70
|
if (this.initialized)
|
|
71
71
|
return;
|
|
72
|
+
// A failed prior replay may have populated only part of the indexes.
|
|
73
|
+
// Every initialization attempt must rebuild from an empty state.
|
|
74
|
+
this.events = [];
|
|
75
|
+
this.aggregateIndex.clear();
|
|
76
|
+
this.aggregateVersions.clear();
|
|
77
|
+
this.snapshots.clear();
|
|
72
78
|
this.ensureDirectory(this.config.logPath);
|
|
73
79
|
// --- events file ---
|
|
74
80
|
if (existsSync(this.config.logPath)) {
|
|
@@ -129,10 +135,10 @@ export class RvfEventLog extends EventEmitter {
|
|
|
129
135
|
const nextVersion = currentVersion + 1;
|
|
130
136
|
event.version = nextVersion;
|
|
131
137
|
// Persist to disk first (crash-safe ordering)
|
|
132
|
-
this.appendRecord(this.config.logPath, event);
|
|
138
|
+
const persisted = this.appendRecord(this.config.logPath, event);
|
|
133
139
|
// Update in-memory state
|
|
134
|
-
this.indexEvent(
|
|
135
|
-
this.emit('event:appended',
|
|
140
|
+
this.indexEvent(persisted);
|
|
141
|
+
this.emit('event:appended', structuredClone(persisted));
|
|
136
142
|
if (nextVersion % this.config.snapshotThreshold === 0) {
|
|
137
143
|
this.emit('snapshot:recommended', {
|
|
138
144
|
aggregateId: event.aggregateId,
|
|
@@ -143,9 +149,9 @@ export class RvfEventLog extends EventEmitter {
|
|
|
143
149
|
/** Save a snapshot for an aggregate. */
|
|
144
150
|
async saveSnapshot(snapshot) {
|
|
145
151
|
this.ensureInitialized();
|
|
146
|
-
this.appendRecord(this.snapshotPath, snapshot);
|
|
147
|
-
this.snapshots.set(
|
|
148
|
-
this.emit('snapshot:saved',
|
|
152
|
+
const persisted = this.appendRecord(this.snapshotPath, snapshot);
|
|
153
|
+
this.snapshots.set(persisted.aggregateId, persisted);
|
|
154
|
+
this.emit('snapshot:saved', structuredClone(persisted));
|
|
149
155
|
}
|
|
150
156
|
// ===========================================================================
|
|
151
157
|
// Read Operations
|
|
@@ -156,7 +162,7 @@ export class RvfEventLog extends EventEmitter {
|
|
|
156
162
|
const indices = this.aggregateIndex.get(aggregateId);
|
|
157
163
|
if (!indices || indices.length === 0)
|
|
158
164
|
return [];
|
|
159
|
-
let result = indices.map((i) => this.events[i]);
|
|
165
|
+
let result = indices.map((i) => structuredClone(this.events[i]));
|
|
160
166
|
if (fromVersion !== undefined) {
|
|
161
167
|
result = result.filter((e) => e.version >= fromVersion);
|
|
162
168
|
}
|
|
@@ -168,9 +174,9 @@ export class RvfEventLog extends EventEmitter {
|
|
|
168
174
|
async getAllEvents(filter) {
|
|
169
175
|
this.ensureInitialized();
|
|
170
176
|
if (!filter) {
|
|
171
|
-
return
|
|
177
|
+
return structuredClone(this.events).sort((a, b) => a.timestamp - b.timestamp);
|
|
172
178
|
}
|
|
173
|
-
let result =
|
|
179
|
+
let result = structuredClone(this.events);
|
|
174
180
|
// Aggregate ID filter
|
|
175
181
|
if (filter.aggregateIds && filter.aggregateIds.length > 0) {
|
|
176
182
|
const set = new Set(filter.aggregateIds);
|
|
@@ -203,7 +209,7 @@ export class RvfEventLog extends EventEmitter {
|
|
|
203
209
|
if (filter.offset) {
|
|
204
210
|
result = result.slice(filter.offset);
|
|
205
211
|
}
|
|
206
|
-
if (filter.limit) {
|
|
212
|
+
if (filter.limit !== undefined) {
|
|
207
213
|
result = result.slice(0, filter.limit);
|
|
208
214
|
}
|
|
209
215
|
return result;
|
|
@@ -211,7 +217,7 @@ export class RvfEventLog extends EventEmitter {
|
|
|
211
217
|
/** Get latest snapshot for an aggregate. */
|
|
212
218
|
async getSnapshot(aggregateId) {
|
|
213
219
|
this.ensureInitialized();
|
|
214
|
-
return this.snapshots.get(aggregateId) ?? null;
|
|
220
|
+
return structuredClone(this.snapshots.get(aggregateId) ?? null);
|
|
215
221
|
}
|
|
216
222
|
/** Return event store statistics. */
|
|
217
223
|
async getStats() {
|
|
@@ -301,10 +307,12 @@ export class RvfEventLog extends EventEmitter {
|
|
|
301
307
|
/** Append a single record to an RVF file. */
|
|
302
308
|
appendRecord(filePath, record) {
|
|
303
309
|
const json = JSON.stringify(record);
|
|
310
|
+
const persisted = JSON.parse(json);
|
|
304
311
|
const payload = Buffer.from(json, 'utf8');
|
|
305
312
|
const lengthBuf = Buffer.allocUnsafe(LENGTH_PREFIX_BYTES);
|
|
306
313
|
lengthBuf.writeUInt32BE(payload.length, 0);
|
|
307
314
|
appendFileSync(filePath, Buffer.concat([lengthBuf, payload]));
|
|
315
|
+
return persisted;
|
|
308
316
|
}
|
|
309
317
|
/** Add an event to the in-memory indexes. */
|
|
310
318
|
indexEvent(event) {
|
|
@@ -112,9 +112,9 @@ export class CircuitBreaker extends EventEmitter {
|
|
|
112
112
|
successes: this.requests.filter((r) => r.success).length,
|
|
113
113
|
totalRequests: this.requests.length,
|
|
114
114
|
rejectedRequests: this.rejectedCount,
|
|
115
|
-
lastFailure: this.lastFailure,
|
|
116
|
-
lastSuccess: this.lastSuccess,
|
|
117
|
-
openSince: this.openedAt,
|
|
115
|
+
lastFailure: this.lastFailure ? new Date(this.lastFailure.getTime()) : null,
|
|
116
|
+
lastSuccess: this.lastSuccess ? new Date(this.lastSuccess.getTime()) : null,
|
|
117
|
+
openSince: this.openedAt ? new Date(this.openedAt.getTime()) : null,
|
|
118
118
|
};
|
|
119
119
|
}
|
|
120
120
|
/**
|
|
@@ -138,6 +138,7 @@ export class CircuitBreaker extends EventEmitter {
|
|
|
138
138
|
* Handle successful request
|
|
139
139
|
*/
|
|
140
140
|
onSuccess() {
|
|
141
|
+
this.cleanOldRequests();
|
|
141
142
|
this.lastSuccess = new Date();
|
|
142
143
|
this.requests.push({ timestamp: Date.now(), success: true });
|
|
143
144
|
if (this.state === CircuitBreakerState.HALF_OPEN) {
|
|
@@ -152,6 +153,7 @@ export class CircuitBreaker extends EventEmitter {
|
|
|
152
153
|
* Handle failed request
|
|
153
154
|
*/
|
|
154
155
|
onFailure(error) {
|
|
156
|
+
this.cleanOldRequests();
|
|
155
157
|
this.lastFailure = new Date();
|
|
156
158
|
this.requests.push({ timestamp: Date.now(), success: false });
|
|
157
159
|
if (this.state === CircuitBreakerState.HALF_OPEN) {
|