Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Security Policy

## Supported Versions

| Version | Supported |
|---------|--------------------|
| 3.5.x | Yes |
| 3.0-3.4 | No |
| 2.x | No |

## Reporting a Vulnerability

**Do not open a public GitHub issue for security vulnerabilities.**

Please report vulnerabilities by emailing **security@ruv.io**. Include the following in your report:

- A clear description of the vulnerability
- Steps to reproduce the issue
- Affected versions and components
- Impact assessment (severity, potential for exploitation)
- Any suggested fixes or mitigations, if available

## Response Timeline

- **48 hours** -- Initial acknowledgment of your report
- **7 days** -- Preliminary assessment and severity classification
- **30 days** -- Target for a fix or mitigation to be released

We will keep you informed of progress throughout the process.

## Safe Harbor

We consider security research conducted in good faith to be authorized activity. We will not pursue legal action against researchers who:

- Make a good faith effort to avoid privacy violations, data destruction, and service disruption
- Report vulnerabilities promptly and provide sufficient detail for reproduction
- Do not publicly disclose the vulnerability before a fix is available
- Do not exploit the vulnerability beyond what is necessary to demonstrate the issue

## Credit

We appreciate the work of security researchers. With your permission, we will publicly credit you in the release notes when a reported vulnerability is fixed.

## Security Practices

This project employs the following security measures at system boundaries:

- **Input validation** using Zod schemas for all public API inputs
- **Parameterized SQL queries** to prevent injection attacks
- **Path traversal prevention** via the `PathValidator` module
- **Command injection protection** via the `SafeExecutor` module

For questions about this policy, contact security@ruv.io.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "claude-flow",
"version": "3.5.39",
"version": "3.5.40",
"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",
"main": "dist/index.js",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion ruflo/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ruflo",
"version": "3.5.39",
"version": "3.5.40",
"description": "Ruflo - Enterprise AI agent orchestration platform. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
"main": "bin/ruflo.js",
"type": "module",
Expand Down
3 changes: 1 addition & 2 deletions v3/@claude-flow/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@claude-flow/cli",
"version": "3.5.39",
"version": "3.5.40",
"type": "module",
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
"main": "dist/src/index.js",
Expand Down Expand Up @@ -75,7 +75,6 @@
"test": "vitest run",
"test:plugin-store": "npx tsx src/plugins/tests/standalone-test.ts",
"test:pattern-store": "npx tsx src/transfer/store/tests/standalone-test.ts",
"preinstall": "node bin/preinstall.cjs || true",
"prepublishOnly": "cp ../../../README.md ./README.md",
"release": "npm version prerelease --preid=alpha && npm run publish:all",
"publish:all": "./scripts/publish.sh"
Expand Down
236 changes: 236 additions & 0 deletions v3/@claude-flow/cli/src/commands/cleanup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
/**
* V3 CLI Cleanup Command
* Removes project artifacts created by claude-flow/ruflo
*
* Created with ruv.io
*/

import type { Command, CommandContext, CommandResult } from '../types.js';
import { output } from '../output.js';
import { existsSync, statSync, rmSync, readdirSync } from 'fs';
import { join } from 'path';

/**
* Artifact directories and files that claude-flow/ruflo may create
*/
const ARTIFACT_DIRS = [
{ path: '.claude', description: 'Claude settings, helpers, agents' },
{ path: '.claude-flow', description: 'Capabilities and configuration' },
{ path: 'data', description: 'Memory databases' },
{ path: '.swarm', description: 'Swarm state' },
{ path: '.hive-mind', description: 'Consensus state' },
{ path: 'coordination', description: 'Coordination data' },
{ path: 'memory', description: 'Memory storage' },
];

const ARTIFACT_FILES = [
{ path: 'claude-flow.config.json', description: 'Claude Flow configuration' },
];

/**
* Paths to preserve when --keep-config is set
*/
const KEEP_CONFIG_PATHS = [
'claude-flow.config.json',
join('.claude', 'settings.json'),
];

/**
* Calculate the total size of a path (file or directory) in bytes
*/
function getSize(fullPath: string): number {
try {
const stat = statSync(fullPath);
if (stat.isFile()) {
return stat.size;
}
if (stat.isDirectory()) {
let total = 0;
const entries = readdirSync(fullPath, { withFileTypes: true });
for (const entry of entries) {
total += getSize(join(fullPath, entry.name));
}
return total;
}
} catch {
// Permission errors, broken symlinks, etc.
}
return 0;
}

/**
* Format bytes into a human-readable string
*/
function formatSize(bytes: number): string {
if (bytes === 0) return '0 B';
const units = ['B', 'KB', 'MB', 'GB'];
const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
const value = bytes / Math.pow(1024, i);
return `${value.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
}

/**
* Cleanup command definition
*/
export const cleanupCommand: Command = {
name: 'cleanup',
description: 'Remove project artifacts created by claude-flow/ruflo',
aliases: ['clean'],
options: [
{
name: 'dry-run',
short: 'n',
description: 'Show what would be removed without deleting (default behavior)',
type: 'boolean',
default: true,
},
{
name: 'force',
short: 'f',
description: 'Actually delete the artifacts',
type: 'boolean',
default: false,
},
{
name: 'keep-config',
short: 'k',
description: 'Preserve claude-flow.config.json and .claude/settings.json',
type: 'boolean',
default: false,
},
],
examples: [
{
command: 'cleanup',
description: 'Show what would be removed (dry run)',
},
{
command: 'cleanup --force',
description: 'Remove all claude-flow artifacts',
},
{
command: 'cleanup --force --keep-config',
description: 'Remove artifacts but keep configuration files',
},
],
action: async (ctx: CommandContext): Promise<CommandResult> => {
const force = ctx.flags.force === true;
const keepConfig = ctx.flags['keep-config'] === true;
const cwd = ctx.cwd;

const dryRun = !force;

output.writeln();
output.writeln(output.bold(dryRun
? 'Claude Flow Cleanup (dry run)'
: 'Claude Flow Cleanup'));
output.writeln();

const found: { path: string; description: string; size: number; type: 'dir' | 'file'; skipped?: boolean }[] = [];
let totalSize = 0;

// Scan directories
for (const artifact of ARTIFACT_DIRS) {
const fullPath = join(cwd, artifact.path);
if (existsSync(fullPath)) {
const size = getSize(fullPath);
found.push({ path: artifact.path, description: artifact.description, size, type: 'dir' });
totalSize += size;
}
}

// Scan files
for (const artifact of ARTIFACT_FILES) {
const fullPath = join(cwd, artifact.path);
if (existsSync(fullPath)) {
const size = getSize(fullPath);
found.push({ path: artifact.path, description: artifact.description, size, type: 'file' });
totalSize += size;
}
}

if (found.length === 0) {
output.writeln(output.info('No claude-flow artifacts found in the current directory.'));
return { success: true, message: 'Nothing to clean' };
}

// Mark items that would be skipped due to --keep-config
if (keepConfig) {
for (const item of found) {
if (KEEP_CONFIG_PATHS.includes(item.path)) {
item.skipped = true;
}
}
}

// Display what was found
output.writeln(output.bold('Artifacts found:'));
output.writeln();

let removedCount = 0;
let removedSize = 0;
let skippedCount = 0;

for (const item of found) {
const sizeStr = formatSize(item.size);
const typeLabel = item.type === 'dir' ? 'dir ' : 'file';

if (item.skipped) {
output.writeln(output.dim(` [skip] ${typeLabel} ${item.path} (${sizeStr}) - ${item.description}`));
skippedCount++;
continue;
}

if (dryRun) {
output.writeln(output.warning(` [would remove] ${typeLabel} ${item.path} (${sizeStr}) - ${item.description}`));
} else {
// Actually delete
try {
const fullPath = join(cwd, item.path);
if (item.type === 'dir') {
rmSync(fullPath, { recursive: true, force: true });
} else {
rmSync(fullPath, { force: true });
}
output.writeln(output.success(` [removed] ${typeLabel} ${item.path} (${sizeStr}) - ${item.description}`));
removedCount++;
removedSize += item.size;
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
output.writeln(output.error(` [failed] ${typeLabel} ${item.path} - ${msg}`));
}
}
}

// Summary
output.writeln();
output.writeln(output.bold('Summary:'));

if (dryRun) {
const actionable = found.filter(f => !f.skipped);
output.writeln(` Found ${actionable.length} artifact(s) totaling ${formatSize(totalSize)}`);
if (skippedCount > 0) {
output.writeln(` ${skippedCount} item(s) would be preserved (--keep-config)`);
}
output.writeln();
output.writeln(output.dim(' This was a dry run. Use --force to actually remove artifacts.'));
} else {
output.writeln(` Removed ${removedCount} artifact(s) totaling ${formatSize(removedSize)}`);
if (skippedCount > 0) {
output.writeln(` Preserved ${skippedCount} item(s) (--keep-config)`);
}
}

output.writeln();

return {
success: true,
message: dryRun
? `Dry run: ${found.length} artifact(s) found`
: `Removed ${removedCount} artifact(s)`,
data: { found, removedCount, removedSize, dryRun },
};
},
};

export default cleanupCommand;
7 changes: 7 additions & 0 deletions v3/@claude-flow/cli/src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const commandLoaders: Record<string, CommandLoader> = {
appliance: () => import('./appliance.js'),
'appliance-advanced': () => import('./appliance-advanced.js'),
'transfer-store': () => import('./transfer-store.js'),
cleanup: () => import('./cleanup.js'),
};

// Cache for loaded commands
Expand Down Expand Up @@ -148,6 +149,7 @@ import updateCommand from './update.js';
import { processCommand } from './process.js';
import { guidanceCommand } from './guidance.js';
import { applianceCommand } from './appliance.js';
import { cleanupCommand } from './cleanup.js';

// Pre-populate cache with core commands
loadedCommands.set('init', initCommand);
Expand All @@ -169,6 +171,7 @@ loadedCommands.set('security', securityCommand);
loadedCommands.set('ruvector', ruvectorCommand);
loadedCommands.set('hive-mind', hiveMindCommand);
loadedCommands.set('guidance', guidanceCommand);
loadedCommands.set('cleanup', cleanupCommand);

// =============================================================================
// Exports (maintain backwards compatibility)
Expand All @@ -195,6 +198,7 @@ export { ruvectorCommand } from './ruvector/index.js';
export { hiveMindCommand } from './hive-mind.js';
export { guidanceCommand } from './guidance.js';
export { applianceCommand } from './appliance.js';
export { cleanupCommand } from './cleanup.js';

// Lazy-loaded command re-exports (for backwards compatibility, but async-only)
export async function getConfigCommand() { return loadCommand('config'); }
Expand All @@ -220,6 +224,7 @@ export async function getIssuesCommand() { return loadCommand('issues'); }
export async function getRuvectorCommand() { return loadCommand('ruvector'); }
export async function getGuidanceCommand() { return loadCommand('guidance'); }
export async function getApplianceCommand() { return loadCommand('appliance'); }
export async function getCleanupCommand() { return loadCommand('cleanup'); }

/**
* Core commands loaded synchronously (available immediately)
Expand All @@ -246,6 +251,7 @@ export const commands: Command[] = [
ruvectorCommand,
hiveMindCommand,
guidanceCommand,
cleanupCommand,
];

/**
Expand Down Expand Up @@ -295,6 +301,7 @@ export const commandsByCategory = {
updateCommand,
processCommand,
applianceCommand,
cleanupCommand,
],
};

Expand Down
Loading
Loading