claude-flow 3.32.11 → 3.32.13
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/package.json +1 -1
- package/v3/@claude-flow/cli/catalog-manifest.json +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +88 -3
- package/v3/@claude-flow/cli/dist/src/commands/swarm.js +42 -1
- package/v3/@claude-flow/cli/dist/src/permission/permission-audit.d.ts +41 -0
- package/v3/@claude-flow/cli/dist/src/permission/permission-audit.js +88 -0
- package/v3/@claude-flow/cli/dist/src/permission/permission-set.d.ts +51 -0
- package/v3/@claude-flow/cli/dist/src/permission/permission-set.js +141 -0
- package/v3/@claude-flow/cli/package.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "3.32.
|
|
3
|
+
"version": "3.32.13",
|
|
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",
|
|
@@ -644,15 +644,54 @@ const routeCommand = {
|
|
|
644
644
|
description: 'Number of top agent suggestions',
|
|
645
645
|
type: 'number',
|
|
646
646
|
default: 3
|
|
647
|
-
}
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
// #2778 dream-cycle — Mixture-of-Agents test-time scaling.
|
|
650
|
+
// When `--mode moa` is set AND the router would have chosen a
|
|
651
|
+
// Tier-3 model (Sonnet/Opus), swap to N parallel Tier-2 (Haiku)
|
|
652
|
+
// calls + majority-vote consensus. Grade-A ACL 2026 SRW evidence
|
|
653
|
+
// (arXiv 2605.01566): +2.7pp accuracy at equal-cost when parallel
|
|
654
|
+
// generations exceed sequential aggregations.
|
|
655
|
+
name: 'mode',
|
|
656
|
+
description: 'Routing mode: single (default) or moa (mixture-of-agents fanout, #2778)',
|
|
657
|
+
type: 'string',
|
|
658
|
+
choices: ['single', 'moa'],
|
|
659
|
+
default: 'single',
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
// #2778 — deliberately NOT `parallel` (short 'p'). Both swarm.ts and
|
|
663
|
+
// workflow.ts declare `--parallel` as a BOOLEAN, and the parser's
|
|
664
|
+
// global boolean-flag registry pollutes subcommand-scoped numeric
|
|
665
|
+
// flags with the same name (parseFlag then treats `--parallel 7` as
|
|
666
|
+
// `parallel:true` and drops the value). Using a distinct name avoids
|
|
667
|
+
// the collision without touching parser semantics that other
|
|
668
|
+
// subcommands rely on.
|
|
669
|
+
name: 'moa-parallel',
|
|
670
|
+
description: 'MoA fanout width (N parallel calls at Haiku tier; only meaningful when --mode=moa)',
|
|
671
|
+
type: 'number',
|
|
672
|
+
default: 3,
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
name: 'consensus',
|
|
676
|
+
description: 'MoA consensus strategy: majority-vote (default) or best-confidence',
|
|
677
|
+
type: 'string',
|
|
678
|
+
choices: ['majority-vote', 'best-confidence'],
|
|
679
|
+
default: 'majority-vote',
|
|
680
|
+
},
|
|
648
681
|
],
|
|
649
682
|
examples: [
|
|
650
|
-
{ command: 'claude-flow hooks route -t "Fix authentication bug"', description: 'Route task to optimal agent' },
|
|
651
|
-
{ command: 'claude-flow hooks route -t "Optimize database queries" -K 5', description: 'Get top 5 suggestions' }
|
|
683
|
+
{ command: 'claude-flow hooks route -t "Fix authentication bug"', description: 'Route task to optimal agent (single mode)' },
|
|
684
|
+
{ command: 'claude-flow hooks route -t "Optimize database queries" -K 5', description: 'Get top 5 suggestions' },
|
|
685
|
+
{ command: 'claude-flow hooks route -t "Design payment webhook" --mode moa --moa-parallel 3', description: 'MoA fanout: 3× Haiku + consensus (dream-cycle #2778)' },
|
|
652
686
|
],
|
|
653
687
|
action: async (ctx) => {
|
|
654
688
|
const task = ctx.flags.task || ctx.args[0];
|
|
655
689
|
const topK = ctx.flags.topK || 3;
|
|
690
|
+
const mode = ctx.flags.mode || 'single';
|
|
691
|
+
// #2778 — flag is --moa-parallel (parser normalizes to camelCase moaParallel)
|
|
692
|
+
// to avoid collision with the boolean --parallel from swarm/workflow.
|
|
693
|
+
const parallel = Math.max(2, ctx.flags.moaParallel || 3);
|
|
694
|
+
const consensus = ctx.flags.consensus || 'majority-vote';
|
|
656
695
|
if (!task) {
|
|
657
696
|
output.printError('Task description is required. Use --task or -t flag.');
|
|
658
697
|
return { success: false, exitCode: 1 };
|
|
@@ -666,6 +705,35 @@ const routeCommand = {
|
|
|
666
705
|
topK,
|
|
667
706
|
includeEstimates: true,
|
|
668
707
|
});
|
|
708
|
+
// #2778 — compute the MoA plan BEFORE the JSON output branch so
|
|
709
|
+
// programmatic callers see `moaPlan` in the JSON result too.
|
|
710
|
+
let moaPlan = null;
|
|
711
|
+
if (mode === 'moa') {
|
|
712
|
+
const complexity = result.estimatedMetrics?.complexity ?? 'medium';
|
|
713
|
+
const wouldTier3 = complexity === 'high';
|
|
714
|
+
const primaryAgent = result.primaryAgent.type;
|
|
715
|
+
const agents = Array.from({ length: parallel }, (_, i) => ({
|
|
716
|
+
name: `moa-${primaryAgent}-${i + 1}`,
|
|
717
|
+
model: 'haiku',
|
|
718
|
+
role: primaryAgent,
|
|
719
|
+
}));
|
|
720
|
+
moaPlan = {
|
|
721
|
+
mode: 'moa',
|
|
722
|
+
parallel,
|
|
723
|
+
tier: 'tier2-haiku',
|
|
724
|
+
consensus,
|
|
725
|
+
rationale: wouldTier3
|
|
726
|
+
? `Complexity=${complexity} would tier-3 (Sonnet/Opus). MoA fanout — ${parallel}× Haiku is typically <1× Sonnet cost (arXiv 2605.01566: +2.7pp at equal cost).`
|
|
727
|
+
: `Complexity=${complexity} — MoA still available as a diversity mechanism, but the single-agent path is usually the cost-optimal choice below Tier-3.`,
|
|
728
|
+
agents,
|
|
729
|
+
synthesizer: {
|
|
730
|
+
name: `moa-synth-${primaryAgent}`,
|
|
731
|
+
model: 'haiku',
|
|
732
|
+
role: 'synthesizer',
|
|
733
|
+
},
|
|
734
|
+
};
|
|
735
|
+
result.moaPlan = moaPlan;
|
|
736
|
+
}
|
|
669
737
|
if (ctx.flags.format === 'json') {
|
|
670
738
|
output.printJson(result);
|
|
671
739
|
return { success: true, data: result };
|
|
@@ -719,6 +787,23 @@ const routeCommand = {
|
|
|
719
787
|
`Complexity: ${result.estimatedMetrics.complexity.toUpperCase()}`
|
|
720
788
|
]);
|
|
721
789
|
}
|
|
790
|
+
// #2778 dream-cycle — render the MoA plan to text output. moaPlan
|
|
791
|
+
// was already spliced into the result above so JSON consumers see it.
|
|
792
|
+
if (mode === 'moa' && moaPlan) {
|
|
793
|
+
const primaryAgent = result.primaryAgent.type;
|
|
794
|
+
output.writeln();
|
|
795
|
+
output.writeln(output.bold('Mixture-of-Agents Plan (#2778)'));
|
|
796
|
+
output.printList([
|
|
797
|
+
`Mode: moa`,
|
|
798
|
+
`Fanout: ${parallel} parallel ${primaryAgent} × Haiku`,
|
|
799
|
+
`Consensus: ${consensus}`,
|
|
800
|
+
`Synthesizer: 1 Haiku agent (${consensus} over ${parallel} verdicts)`,
|
|
801
|
+
]);
|
|
802
|
+
output.writeln();
|
|
803
|
+
output.printBox(`Spawn ${parallel} background Task calls with model="haiku" and subagent_type="${primaryAgent}"\n` +
|
|
804
|
+
`then a synthesizer Task ("moa-synth-${primaryAgent}") that reads all ${parallel} results and picks the ${consensus === 'majority-vote' ? 'majority answer' : 'highest-confidence answer'}.\n\n` +
|
|
805
|
+
`Cost note: ${parallel}× Haiku is typically <1× Sonnet on comparable complexity — see arXiv 2605.01566 for the ACL 2026 evidence.`, 'MoA Execution Directive');
|
|
806
|
+
}
|
|
722
807
|
return { success: true, data: result };
|
|
723
808
|
}
|
|
724
809
|
catch (error) {
|
|
@@ -300,12 +300,23 @@ const initCommand = {
|
|
|
300
300
|
description: 'Enable V3 15-agent hierarchical mesh mode',
|
|
301
301
|
type: 'boolean',
|
|
302
302
|
default: false
|
|
303
|
-
}
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
// #2768 — dream-cycle SubagentPermissionDelegate. Ships a per-role
|
|
306
|
+
// capability manifest to `.swarm/permissions.jsonl` + an append-only
|
|
307
|
+
// audit trail. Task-tool prompts can consult the manifest; ruflo
|
|
308
|
+
// does NOT enforce at the syscall boundary (Claude Code owns that).
|
|
309
|
+
name: 'with-permissions',
|
|
310
|
+
description: 'Ship workspace-scoped permission manifest (preset: strict|standard|permissive) — dream-cycle #2768',
|
|
311
|
+
type: 'string',
|
|
312
|
+
choices: ['strict', 'standard', 'permissive'],
|
|
313
|
+
},
|
|
304
314
|
],
|
|
305
315
|
action: async (ctx) => {
|
|
306
316
|
let topology = ctx.flags.topology;
|
|
307
317
|
const maxAgents = ctx.flags.maxAgents || 15;
|
|
308
318
|
const v3Mode = ctx.flags.v3Mode;
|
|
319
|
+
const withPermissions = ctx.flags.withPermissions;
|
|
309
320
|
// V3 mode enables hierarchical-mesh hybrid
|
|
310
321
|
if (v3Mode) {
|
|
311
322
|
topology = 'hierarchical-mesh';
|
|
@@ -377,6 +388,7 @@ const initCommand = {
|
|
|
377
388
|
maxAgents: result.config.maxAgents,
|
|
378
389
|
strategy: ctx.flags.strategy || 'development',
|
|
379
390
|
v3Mode,
|
|
391
|
+
permissions: withPermissions ?? null,
|
|
380
392
|
initializedAt: result.initializedAt,
|
|
381
393
|
status: 'ready'
|
|
382
394
|
}, null, 2));
|
|
@@ -384,6 +396,35 @@ const initCommand = {
|
|
|
384
396
|
catch {
|
|
385
397
|
// Ignore errors writing state file
|
|
386
398
|
}
|
|
399
|
+
// #2768 — write the permission manifest + seed the audit trail.
|
|
400
|
+
// Optional: only fires when --with-permissions was passed. Missing
|
|
401
|
+
// module or failed I/O is non-critical; we log a dim warning and
|
|
402
|
+
// continue so a broken permission layer never blocks swarm init.
|
|
403
|
+
if (withPermissions) {
|
|
404
|
+
try {
|
|
405
|
+
const [{ resolvePreset }, { writeGrants, appendAuditEvent }] = await Promise.all([
|
|
406
|
+
import('../permission/permission-set.js'),
|
|
407
|
+
import('../permission/permission-audit.js'),
|
|
408
|
+
]);
|
|
409
|
+
const sets = resolvePreset(withPermissions);
|
|
410
|
+
writeGrants(sets, swarmDir);
|
|
411
|
+
for (const set of sets) {
|
|
412
|
+
appendAuditEvent({
|
|
413
|
+
agentId: 'swarm-init',
|
|
414
|
+
role: set.role,
|
|
415
|
+
event: 'granted',
|
|
416
|
+
capability: `preset:${withPermissions}`,
|
|
417
|
+
swarmId: result.swarmId,
|
|
418
|
+
reason: `Initial grant from swarm init --with-permissions ${withPermissions}`,
|
|
419
|
+
}, swarmDir);
|
|
420
|
+
}
|
|
421
|
+
output.writeln(output.dim(` Wrote permission manifest (preset: ${withPermissions}, ${sets.length} roles) → .swarm/permissions.jsonl`));
|
|
422
|
+
output.writeln(output.dim(` Audit trail seeded → .swarm/permission-audit.jsonl`));
|
|
423
|
+
}
|
|
424
|
+
catch (err) {
|
|
425
|
+
output.writeln(output.dim(` ⚠ permission manifest skipped: ${err instanceof Error ? err.message : String(err)}`));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
387
428
|
if (ctx.flags.format === 'json') {
|
|
388
429
|
output.printJson(result);
|
|
389
430
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Append-only permission audit log for swarm subagents (dream-cycle #2768).
|
|
3
|
+
*
|
|
4
|
+
* Every grant / check / deny / revoke event is written to
|
|
5
|
+
* `.swarm/permission-audit.jsonl` — one JSON object per line, no rewrites,
|
|
6
|
+
* no deletes. Persistence follows the same discipline as ruflo's other
|
|
7
|
+
* append-only ledgers (routing-outcomes, funnel-events) so downstream
|
|
8
|
+
* readers can tail without truncation risk.
|
|
9
|
+
*/
|
|
10
|
+
/** A single permission-audit event. */
|
|
11
|
+
export interface AuditEvent {
|
|
12
|
+
/** ISO-8601 timestamp. */
|
|
13
|
+
timestamp: string;
|
|
14
|
+
/** Opaque agent identifier — matches Task tool `name:` or a swarm-assigned slot id. */
|
|
15
|
+
agentId: string;
|
|
16
|
+
/** Symbolic role — matches PermissionSet.role. */
|
|
17
|
+
role: string;
|
|
18
|
+
/** Event class. */
|
|
19
|
+
event: 'granted' | 'checked' | 'denied' | 'revoked';
|
|
20
|
+
/** Free-form capability descriptor (`tool:Bash`, `path:src/**`, `net:api.github.com`). */
|
|
21
|
+
capability: string;
|
|
22
|
+
/** Optional swarm-scope identifier. */
|
|
23
|
+
swarmId?: string;
|
|
24
|
+
/** Optional human-readable reason. */
|
|
25
|
+
reason?: string;
|
|
26
|
+
/** Auto-generated event id for correlation. */
|
|
27
|
+
eventId: string;
|
|
28
|
+
}
|
|
29
|
+
/** Resolve `.swarm/permission-audit.jsonl` under the given (or cwd's) swarm dir. */
|
|
30
|
+
export declare function auditLogPath(swarmDir?: string): string;
|
|
31
|
+
/** Resolve `.swarm/permissions.jsonl` — the current-grants manifest. */
|
|
32
|
+
export declare function grantsPath(swarmDir?: string): string;
|
|
33
|
+
/** Append a single audit event. Never throws — failures are non-critical. */
|
|
34
|
+
export declare function appendAuditEvent(event: Omit<AuditEvent, 'eventId' | 'timestamp'>, swarmDir?: string): void;
|
|
35
|
+
/** Read every audit event. Used by review tooling. */
|
|
36
|
+
export declare function readAuditLog(swarmDir?: string): AuditEvent[];
|
|
37
|
+
/** Write the current grants manifest (overwrites) — one line per role. */
|
|
38
|
+
export declare function writeGrants(grants: unknown[], swarmDir?: string): void;
|
|
39
|
+
/** Read the current grants manifest. */
|
|
40
|
+
export declare function readGrants(swarmDir?: string): unknown[];
|
|
41
|
+
//# sourceMappingURL=permission-audit.d.ts.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Append-only permission audit log for swarm subagents (dream-cycle #2768).
|
|
3
|
+
*
|
|
4
|
+
* Every grant / check / deny / revoke event is written to
|
|
5
|
+
* `.swarm/permission-audit.jsonl` — one JSON object per line, no rewrites,
|
|
6
|
+
* no deletes. Persistence follows the same discipline as ruflo's other
|
|
7
|
+
* append-only ledgers (routing-outcomes, funnel-events) so downstream
|
|
8
|
+
* readers can tail without truncation risk.
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from 'node:fs';
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
import { randomBytes } from 'node:crypto';
|
|
13
|
+
/** Resolve `.swarm/permission-audit.jsonl` under the given (or cwd's) swarm dir. */
|
|
14
|
+
export function auditLogPath(swarmDir) {
|
|
15
|
+
const dir = swarmDir ?? path.join(process.cwd(), '.swarm');
|
|
16
|
+
return path.join(dir, 'permission-audit.jsonl');
|
|
17
|
+
}
|
|
18
|
+
/** Resolve `.swarm/permissions.jsonl` — the current-grants manifest. */
|
|
19
|
+
export function grantsPath(swarmDir) {
|
|
20
|
+
const dir = swarmDir ?? path.join(process.cwd(), '.swarm');
|
|
21
|
+
return path.join(dir, 'permissions.jsonl');
|
|
22
|
+
}
|
|
23
|
+
function ensureDirFor(filePath) {
|
|
24
|
+
const dir = path.dirname(filePath);
|
|
25
|
+
if (!fs.existsSync(dir))
|
|
26
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
/** Append a single audit event. Never throws — failures are non-critical. */
|
|
29
|
+
export function appendAuditEvent(event, swarmDir) {
|
|
30
|
+
const full = {
|
|
31
|
+
...event,
|
|
32
|
+
timestamp: new Date().toISOString(),
|
|
33
|
+
eventId: `evt-${Date.now()}-${randomBytes(4).toString('hex')}`,
|
|
34
|
+
};
|
|
35
|
+
try {
|
|
36
|
+
const p = auditLogPath(swarmDir);
|
|
37
|
+
ensureDirFor(p);
|
|
38
|
+
fs.appendFileSync(p, JSON.stringify(full) + '\n', 'utf-8');
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
/* audit log unavailable — non-critical, don't crash the caller */
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Read every audit event. Used by review tooling. */
|
|
45
|
+
export function readAuditLog(swarmDir) {
|
|
46
|
+
const p = auditLogPath(swarmDir);
|
|
47
|
+
if (!fs.existsSync(p))
|
|
48
|
+
return [];
|
|
49
|
+
const raw = fs.readFileSync(p, 'utf-8');
|
|
50
|
+
return raw
|
|
51
|
+
.split('\n')
|
|
52
|
+
.filter((line) => line.length > 0)
|
|
53
|
+
.map((line) => {
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(line);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
.filter((e) => e !== null);
|
|
62
|
+
}
|
|
63
|
+
/** Write the current grants manifest (overwrites) — one line per role. */
|
|
64
|
+
export function writeGrants(grants, swarmDir) {
|
|
65
|
+
const p = grantsPath(swarmDir);
|
|
66
|
+
ensureDirFor(p);
|
|
67
|
+
fs.writeFileSync(p, grants.map((g) => JSON.stringify(g)).join('\n') + '\n', 'utf-8');
|
|
68
|
+
}
|
|
69
|
+
/** Read the current grants manifest. */
|
|
70
|
+
export function readGrants(swarmDir) {
|
|
71
|
+
const p = grantsPath(swarmDir);
|
|
72
|
+
if (!fs.existsSync(p))
|
|
73
|
+
return [];
|
|
74
|
+
const raw = fs.readFileSync(p, 'utf-8');
|
|
75
|
+
return raw
|
|
76
|
+
.split('\n')
|
|
77
|
+
.filter((line) => line.length > 0)
|
|
78
|
+
.map((line) => {
|
|
79
|
+
try {
|
|
80
|
+
return JSON.parse(line);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
.filter((g) => g !== null);
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=permission-audit.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace-scoped permission model for swarm subagents (dream-cycle #2768,
|
|
3
|
+
* ClawArena finding: privilege granting is the #1 orchestration bottleneck,
|
|
4
|
+
* no LLM > 50% workspace-permission precision as team lead).
|
|
5
|
+
*
|
|
6
|
+
* SCOPE (honest): this is a METADATA + AUDIT layer, not a runtime sandbox.
|
|
7
|
+
* Claude Code's Task tool owns the actual subprocess sandbox; ruflo cannot
|
|
8
|
+
* enforce at the syscall boundary. What we CAN do:
|
|
9
|
+
* 1. Publish a per-role capability manifest that Task-tool prompts can
|
|
10
|
+
* consult ("your workspace-scoped tools are X, Y, Z; you may not use W").
|
|
11
|
+
* 2. Record every grant/check/deny/revoke to an append-only audit trail
|
|
12
|
+
* so the swarm has a reviewable permission history.
|
|
13
|
+
*
|
|
14
|
+
* That is enough to close the "permission-hygiene" half of the ClawArena
|
|
15
|
+
* finding — the "50% precision as team lead" number is about the LLM's
|
|
16
|
+
* decision QUALITY, which no runtime layer fixes.
|
|
17
|
+
*/
|
|
18
|
+
import type { PathValidator } from '@claude-flow/security';
|
|
19
|
+
/** A single agent role's capability envelope. */
|
|
20
|
+
export interface PermissionSet {
|
|
21
|
+
/** Symbolic role name — matches ruflo agent-type names (coder, tester, reviewer, …). */
|
|
22
|
+
role: string;
|
|
23
|
+
/** Tools this role MAY use. Empty array = deny-all-tools. */
|
|
24
|
+
allowedTools: string[];
|
|
25
|
+
/** Tools this role MUST NOT use (evaluated after allow — deny wins). */
|
|
26
|
+
deniedTools: string[];
|
|
27
|
+
/** Glob patterns this role MAY read/write, relative to swarm cwd. */
|
|
28
|
+
allowedPaths: string[];
|
|
29
|
+
/** Glob patterns this role MUST NOT touch (deny wins). */
|
|
30
|
+
deniedPaths: string[];
|
|
31
|
+
/** Exact-host allowlist for outbound network. Empty = deny-all-network. */
|
|
32
|
+
allowedNetworkHosts: string[];
|
|
33
|
+
/** Human-readable note explaining the intent of this envelope. */
|
|
34
|
+
notes?: string;
|
|
35
|
+
}
|
|
36
|
+
/** Built-in presets, ordered narrow → wide. Each preset ships per-role sets. */
|
|
37
|
+
export declare const PRESETS: Record<'strict' | 'standard' | 'permissive', PermissionSet[]>;
|
|
38
|
+
export type PresetName = keyof typeof PRESETS;
|
|
39
|
+
/**
|
|
40
|
+
* Resolve a preset name to its per-role sets. Throws on unknown preset —
|
|
41
|
+
* fail loud so a typo doesn't silently degrade to permissive.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolvePreset(name: string): PermissionSet[];
|
|
44
|
+
/**
|
|
45
|
+
* Sanity-check a permission set: role name shape, no null entries.
|
|
46
|
+
* Path validation is deferred to the enforcement side (PathValidator)
|
|
47
|
+
* so this module has no @claude-flow/security runtime dep beyond a
|
|
48
|
+
* type-only import.
|
|
49
|
+
*/
|
|
50
|
+
export declare function validatePermissionSet(set: PermissionSet, _validator?: PathValidator): string[];
|
|
51
|
+
//# sourceMappingURL=permission-set.d.ts.map
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace-scoped permission model for swarm subagents (dream-cycle #2768,
|
|
3
|
+
* ClawArena finding: privilege granting is the #1 orchestration bottleneck,
|
|
4
|
+
* no LLM > 50% workspace-permission precision as team lead).
|
|
5
|
+
*
|
|
6
|
+
* SCOPE (honest): this is a METADATA + AUDIT layer, not a runtime sandbox.
|
|
7
|
+
* Claude Code's Task tool owns the actual subprocess sandbox; ruflo cannot
|
|
8
|
+
* enforce at the syscall boundary. What we CAN do:
|
|
9
|
+
* 1. Publish a per-role capability manifest that Task-tool prompts can
|
|
10
|
+
* consult ("your workspace-scoped tools are X, Y, Z; you may not use W").
|
|
11
|
+
* 2. Record every grant/check/deny/revoke to an append-only audit trail
|
|
12
|
+
* so the swarm has a reviewable permission history.
|
|
13
|
+
*
|
|
14
|
+
* That is enough to close the "permission-hygiene" half of the ClawArena
|
|
15
|
+
* finding — the "50% precision as team lead" number is about the LLM's
|
|
16
|
+
* decision QUALITY, which no runtime layer fixes.
|
|
17
|
+
*/
|
|
18
|
+
/** Built-in presets, ordered narrow → wide. Each preset ships per-role sets. */
|
|
19
|
+
export const PRESETS = {
|
|
20
|
+
/**
|
|
21
|
+
* strict — read-only file access, no network, no bash. Suitable for
|
|
22
|
+
* research / review / planning agents. Denies every destructive surface.
|
|
23
|
+
*/
|
|
24
|
+
strict: [
|
|
25
|
+
{
|
|
26
|
+
role: 'researcher',
|
|
27
|
+
allowedTools: ['Read', 'Grep', 'Glob'],
|
|
28
|
+
deniedTools: ['Bash', 'Edit', 'Write', 'NotebookEdit'],
|
|
29
|
+
allowedPaths: ['**/*'],
|
|
30
|
+
deniedPaths: ['**/.env', '**/.env.*', '**/secrets/**', '**/*.key', '**/*.pem'],
|
|
31
|
+
allowedNetworkHosts: [],
|
|
32
|
+
notes: 'Read-only research role; no filesystem writes, no network, no shell.',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
role: 'reviewer',
|
|
36
|
+
allowedTools: ['Read', 'Grep', 'Glob'],
|
|
37
|
+
deniedTools: ['Bash', 'Edit', 'Write', 'NotebookEdit'],
|
|
38
|
+
allowedPaths: ['**/*'],
|
|
39
|
+
deniedPaths: ['**/.env', '**/.env.*', '**/secrets/**', '**/*.key', '**/*.pem'],
|
|
40
|
+
allowedNetworkHosts: [],
|
|
41
|
+
notes: 'Read-only code review; findings emitted via return message, not filesystem writes.',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
role: 'planner',
|
|
45
|
+
allowedTools: ['Read', 'Grep', 'Glob'],
|
|
46
|
+
deniedTools: ['Bash', 'Edit', 'Write', 'NotebookEdit'],
|
|
47
|
+
allowedPaths: ['**/*'],
|
|
48
|
+
deniedPaths: ['**/.env', '**/.env.*', '**/secrets/**', '**/*.key', '**/*.pem'],
|
|
49
|
+
allowedNetworkHosts: [],
|
|
50
|
+
notes: 'Read-only planning; produces plan text, no side effects.',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
/**
|
|
54
|
+
* standard — the default. Read + edit under cwd, no bash, allowlisted
|
|
55
|
+
* network hosts for common dev workflows (github, npm registry, docs).
|
|
56
|
+
* Suitable for implementation agents doing bounded file changes.
|
|
57
|
+
*/
|
|
58
|
+
standard: [
|
|
59
|
+
{
|
|
60
|
+
role: 'coder',
|
|
61
|
+
allowedTools: ['Read', 'Grep', 'Glob', 'Edit', 'Write', 'NotebookEdit'],
|
|
62
|
+
deniedTools: ['Bash'],
|
|
63
|
+
allowedPaths: ['src/**', 'tests/**', '__tests__/**', 'lib/**', 'plugins/**', 'v3/**', 'docs/**', 'CHANGELOG.md', 'package.json'],
|
|
64
|
+
deniedPaths: ['**/.env', '**/.env.*', '**/secrets/**', '**/*.key', '**/*.pem', '**/.git/**', '**/node_modules/**'],
|
|
65
|
+
allowedNetworkHosts: ['api.github.com', 'registry.npmjs.org', 'docs.rs', 'crates.io'],
|
|
66
|
+
notes: 'Bounded code writer; edits under source directories, no shell, allowlist network.',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
role: 'tester',
|
|
70
|
+
allowedTools: ['Read', 'Grep', 'Glob', 'Edit', 'Write'],
|
|
71
|
+
deniedTools: ['Bash', 'NotebookEdit'],
|
|
72
|
+
allowedPaths: ['tests/**', '__tests__/**', '**/*.test.*', '**/*.spec.*'],
|
|
73
|
+
deniedPaths: ['**/.env', '**/.env.*', '**/secrets/**', '**/*.key', '**/*.pem'],
|
|
74
|
+
allowedNetworkHosts: [],
|
|
75
|
+
notes: 'Tests-only writer; scope pinned to test directories and .test/.spec files.',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
role: 'reviewer',
|
|
79
|
+
allowedTools: ['Read', 'Grep', 'Glob'],
|
|
80
|
+
deniedTools: ['Bash', 'Edit', 'Write', 'NotebookEdit'],
|
|
81
|
+
allowedPaths: ['**/*'],
|
|
82
|
+
deniedPaths: ['**/.env', '**/.env.*', '**/secrets/**', '**/*.key', '**/*.pem'],
|
|
83
|
+
allowedNetworkHosts: [],
|
|
84
|
+
notes: 'Read-only reviewer; identical to strict-reviewer.',
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
/**
|
|
88
|
+
* permissive — the pre-#2768 baseline. Full toolset, no path fencing.
|
|
89
|
+
* Kept for compatibility so users can opt back in explicitly. NOT a
|
|
90
|
+
* good default — the entire point of this feature is that the LLM's
|
|
91
|
+
* unaided permission decisions land under 50% precision.
|
|
92
|
+
*/
|
|
93
|
+
permissive: [
|
|
94
|
+
{
|
|
95
|
+
role: '*',
|
|
96
|
+
allowedTools: ['*'],
|
|
97
|
+
deniedTools: [],
|
|
98
|
+
allowedPaths: ['**/*'],
|
|
99
|
+
deniedPaths: [],
|
|
100
|
+
allowedNetworkHosts: ['*'],
|
|
101
|
+
notes: 'No restriction. Kept for opt-in compatibility; see #2768 for why this is not the default.',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Resolve a preset name to its per-role sets. Throws on unknown preset —
|
|
107
|
+
* fail loud so a typo doesn't silently degrade to permissive.
|
|
108
|
+
*/
|
|
109
|
+
export function resolvePreset(name) {
|
|
110
|
+
if (!(name in PRESETS)) {
|
|
111
|
+
const valid = Object.keys(PRESETS).join(', ');
|
|
112
|
+
throw new Error(`Unknown permissions preset: ${name}. Valid presets: ${valid}`);
|
|
113
|
+
}
|
|
114
|
+
return PRESETS[name];
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Sanity-check a permission set: role name shape, no null entries.
|
|
118
|
+
* Path validation is deferred to the enforcement side (PathValidator)
|
|
119
|
+
* so this module has no @claude-flow/security runtime dep beyond a
|
|
120
|
+
* type-only import.
|
|
121
|
+
*/
|
|
122
|
+
export function validatePermissionSet(set, _validator) {
|
|
123
|
+
const errors = [];
|
|
124
|
+
if (!set.role || typeof set.role !== 'string')
|
|
125
|
+
errors.push('role must be a non-empty string');
|
|
126
|
+
const arrays = [
|
|
127
|
+
['allowedTools', set.allowedTools],
|
|
128
|
+
['deniedTools', set.deniedTools],
|
|
129
|
+
['allowedPaths', set.allowedPaths],
|
|
130
|
+
['deniedPaths', set.deniedPaths],
|
|
131
|
+
['allowedNetworkHosts', set.allowedNetworkHosts],
|
|
132
|
+
];
|
|
133
|
+
for (const [key, val] of arrays) {
|
|
134
|
+
if (!Array.isArray(val))
|
|
135
|
+
errors.push(`${String(key)} must be an array`);
|
|
136
|
+
else if (val.some((v) => typeof v !== 'string'))
|
|
137
|
+
errors.push(`${String(key)} must contain only strings`);
|
|
138
|
+
}
|
|
139
|
+
return errors;
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=permission-set.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.32.
|
|
3
|
+
"version": "3.32.13",
|
|
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",
|