claude-flow 3.50.0 → 3.51.0

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.
Files changed (60) hide show
  1. package/.claude/proven-config.json +1 -1
  2. package/.claude/settings.json +11 -2
  3. package/.claude-plugin/marketplace.json +5 -0
  4. package/package.json +1 -1
  5. package/v3/@claude-flow/cli/README.md +0 -2
  6. package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
  7. package/v3/@claude-flow/cli/dist/src/commands/catalog.d.ts +18 -0
  8. package/v3/@claude-flow/cli/dist/src/commands/catalog.js +89 -0
  9. package/v3/@claude-flow/cli/dist/src/commands/doctor.js +8 -3
  10. package/v3/@claude-flow/cli/dist/src/commands/index.js +3 -0
  11. package/v3/@claude-flow/cli/dist/src/commands/init.js +50 -11
  12. package/v3/@claude-flow/cli/dist/src/commands/mission.d.ts +24 -0
  13. package/v3/@claude-flow/cli/dist/src/commands/mission.js +116 -0
  14. package/v3/@claude-flow/cli/dist/src/commands/mods.d.ts +1 -1
  15. package/v3/@claude-flow/cli/dist/src/commands/mods.js +67 -23
  16. package/v3/@claude-flow/cli/dist/src/mcp-client.js +3 -0
  17. package/v3/@claude-flow/cli/dist/src/mcp-tools/capability-brain.js +1 -1
  18. package/v3/@claude-flow/cli/dist/src/mcp-tools/index.d.ts +1 -0
  19. package/v3/@claude-flow/cli/dist/src/mcp-tools/index.js +2 -0
  20. package/v3/@claude-flow/cli/dist/src/mcp-tools/mission-tools.d.ts +22 -0
  21. package/v3/@claude-flow/cli/dist/src/mcp-tools/mission-tools.js +75 -0
  22. package/v3/@claude-flow/cli/dist/src/missions/fold.d.ts +19 -0
  23. package/v3/@claude-flow/cli/dist/src/missions/fold.js +238 -0
  24. package/v3/@claude-flow/cli/dist/src/missions/index.d.ts +10 -0
  25. package/v3/@claude-flow/cli/dist/src/missions/index.js +10 -0
  26. package/v3/@claude-flow/cli/dist/src/missions/observation.d.ts +70 -0
  27. package/v3/@claude-flow/cli/dist/src/missions/observation.js +77 -0
  28. package/v3/@claude-flow/cli/dist/src/missions/schemas.d.ts +735 -0
  29. package/v3/@claude-flow/cli/dist/src/missions/schemas.js +150 -0
  30. package/v3/@claude-flow/cli/dist/src/missions/service.d.ts +88 -0
  31. package/v3/@claude-flow/cli/dist/src/missions/service.js +278 -0
  32. package/v3/@claude-flow/cli/dist/src/missions/store.d.ts +68 -0
  33. package/v3/@claude-flow/cli/dist/src/missions/store.js +209 -0
  34. package/v3/@claude-flow/cli/dist/src/missions/transitions.d.ts +81 -0
  35. package/v3/@claude-flow/cli/dist/src/missions/transitions.js +178 -0
  36. package/v3/@claude-flow/cli/dist/src/mods/apply.d.ts +50 -0
  37. package/v3/@claude-flow/cli/dist/src/mods/apply.js +186 -0
  38. package/v3/@claude-flow/cli/dist/src/mods/command-registry/catalog.d.ts +45 -0
  39. package/v3/@claude-flow/cli/dist/src/mods/command-registry/catalog.js +234 -0
  40. package/v3/@claude-flow/cli/dist/src/mods/command-registry/engine-report.d.ts +66 -0
  41. package/v3/@claude-flow/cli/dist/src/mods/command-registry/engine-report.js +119 -0
  42. package/v3/@claude-flow/cli/dist/src/mods/command-registry/generate.d.ts +51 -0
  43. package/v3/@claude-flow/cli/dist/src/mods/command-registry/generate.js +166 -0
  44. package/v3/@claude-flow/cli/dist/src/mods/command-registry/index.d.ts +9 -0
  45. package/v3/@claude-flow/cli/dist/src/mods/command-registry/index.js +9 -0
  46. package/v3/@claude-flow/cli/dist/src/mods/command-registry/inventory.d.ts +63 -0
  47. package/v3/@claude-flow/cli/dist/src/mods/command-registry/inventory.js +230 -0
  48. package/v3/@claude-flow/cli/dist/src/mods/command-registry/schema.d.ts +836 -0
  49. package/v3/@claude-flow/cli/dist/src/mods/command-registry/schema.js +137 -0
  50. package/v3/@claude-flow/cli/dist/src/mods/install.d.ts +60 -22
  51. package/v3/@claude-flow/cli/dist/src/mods/install.js +91 -41
  52. package/v3/@claude-flow/cli/dist/src/mods/plugin-repair.d.ts +81 -0
  53. package/v3/@claude-flow/cli/dist/src/mods/plugin-repair.js +120 -0
  54. package/v3/@claude-flow/cli/dist/src/mods/plugin-resolve.d.ts +63 -0
  55. package/v3/@claude-flow/cli/dist/src/mods/plugin-resolve.js +182 -0
  56. package/v3/@claude-flow/cli/dist/src/mods/probe.d.ts +5 -0
  57. package/v3/@claude-flow/cli/dist/src/mods/probe.js +28 -3
  58. package/v3/@claude-flow/cli/dist/src/services/policy-runtime.d.ts +5 -0
  59. package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +5 -1
  60. package/v3/@claude-flow/cli/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  {
2
- "adoptedAt": 1790898945808,
2
+ "adoptedAt": 1790916839199,
3
3
  "championId": "sha256:6141a8ea990c5063b77e090ae8f37f9c539d8aa8f58dcceb30f3a82f97e57319",
4
4
  "manifest": {
5
5
  "schema": "ruflo.proven-config/v1",
@@ -5,7 +5,8 @@
5
5
  "env": {
6
6
  "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1",
7
7
  "CLAUDE_FLOW_V3_ENABLED": "true",
8
- "CLAUDE_FLOW_HOOKS_ENABLED": "true"
8
+ "CLAUDE_FLOW_HOOKS_ENABLED": "true",
9
+ "CLAUDE_CODE_ENABLE_FUNCTION_HOOKS": "1"
9
10
  },
10
11
  "permissions": {
11
12
  "allow": [
@@ -280,5 +281,13 @@
280
281
  },
281
282
  "enabledMcpjsonServers": [
282
283
  "claude-flow"
283
- ]
284
+ ],
285
+ "enabledPlugins": {
286
+ "ruflo-mods@ruflo": true,
287
+ "ruflo-swarm@ruflo": true,
288
+ "ruflo-console@ruflo": true
289
+ },
290
+ "extraKnownMarketplaces": {
291
+ "ruflo": { "source": { "source": "github", "repo": "ruvnet/ruflo" } }
292
+ }
284
293
  }
@@ -225,6 +225,11 @@
225
225
  "name": "ruflo-ruos",
226
226
  "source": "./plugins/ruflo-ruos",
227
227
  "description": "Run ruflo swarm agents on your own ruOS cloud desktops via the tenant-authenticated fleet MCP or per-tenant SSH; optional, removable"
228
+ },
229
+ {
230
+ "name": "ruflo-console",
231
+ "source": "./plugins/ruflo-console",
232
+ "description": "Early access: ruflo's cockpit as a Claude Code mod (function hooks) and the one /ruflo command for every ruflo mod \u2014 overview with health alerts, swarm topology, claims flow, federation map, plugin health, learning pipeline, MetaHarness radar, memory, cost gauge, agent timeline, approvals and events; a command palette for confirm-gated actions through the ruflo CLI; auto-opens where it docks; no network by default"
228
233
  }
229
234
  ]
230
235
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "3.50.0",
3
+ "version": "3.51.0",
4
4
  "workspaces": [
5
5
  "v3/@claude-flow/codex",
6
6
  "v3/@claude-flow/mcp",
@@ -16,8 +16,6 @@
16
16
 
17
17
  # Ruflo
18
18
 
19
- [English](README.md) · [简体中文](README.zh-CN.md)
20
-
21
19
  **An agent meta-harness for Claude Code and Codex.**
22
20
 
23
21
  [![RuFlo Explained — build an AI team that plans, remembers, tests, and improves](docs/assets/ruflo-explained/ch14.jpg)](docs/ruflo-explained.md)
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generation": 7,
4
- "generatedAt": "2026-10-02T00:19:27.102Z",
5
- "gitSha": "27982983",
3
+ "generation": 6,
4
+ "generatedAt": "2026-09-17T21:28:47.530Z",
5
+ "gitSha": "702d1461",
6
6
  "catalog": {
7
- "agents": 176,
7
+ "agents": 167,
8
8
  "tools": 418,
9
9
  "skills": 34
10
10
  },
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `ruflo catalog` — ADR-406 P0/P1 command inventory and canonical catalog.
3
+ *
4
+ * Interim home: ADR-406 §14 places these under `ruflo mods catalog`, but
5
+ * `commands/mods.ts` belongs to the install-fix lane (PR #3612) until it
6
+ * merges. This file moves there as a follow-up; the operations stay the same.
7
+ *
8
+ * catalog generate [--check] inventory + engine probe + deterministic catalog
9
+ * catalog verify [--file] schema, duplicate ids/names, digest parity
10
+ * catalog engine engine capability report only
11
+ *
12
+ * Reads command files; writes only the catalog and engine report under
13
+ * src/mods/command-registry/. Never touches a command file.
14
+ */
15
+ import type { Command } from '../types.js';
16
+ export declare const catalogCommand: Command;
17
+ export default catalogCommand;
18
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1,89 @@
1
+ /**
2
+ * `ruflo catalog` — ADR-406 P0/P1 command inventory and canonical catalog.
3
+ *
4
+ * Interim home: ADR-406 §14 places these under `ruflo mods catalog`, but
5
+ * `commands/mods.ts` belongs to the install-fix lane (PR #3612) until it
6
+ * merges. This file moves there as a follow-up; the operations stay the same.
7
+ *
8
+ * catalog generate [--check] inventory + engine probe + deterministic catalog
9
+ * catalog verify [--file] schema, duplicate ids/names, digest parity
10
+ * catalog engine engine capability report only
11
+ *
12
+ * Reads command files; writes only the catalog and engine report under
13
+ * src/mods/command-registry/. Never touches a command file.
14
+ */
15
+ import { existsSync, readFileSync } from 'node:fs';
16
+ import { homedir } from 'node:os';
17
+ import { join, resolve } from 'node:path';
18
+ import { output } from '../output.js';
19
+ import { CATALOG_RELATIVE_PATH, ENGINE_REPORT_RELATIVE_PATH, generate, serializeCatalog, summarize, verifyCatalog, writeCatalog, } from '../mods/command-registry/index.js';
20
+ import { canonicalJson, sha256Hex } from '../mods/command-registry/index.js';
21
+ import { writeFileSync } from 'node:fs';
22
+ function engineFrom(flags) {
23
+ const explicit = typeof flags.engine === 'string' ? flags.engine : undefined;
24
+ const candidate = explicit ?? join(homedir(), '.local', 'bin', 'claude');
25
+ return existsSync(candidate) ? candidate : null;
26
+ }
27
+ function finish(data, ok, json, text) {
28
+ if (json)
29
+ output.writeln(JSON.stringify(data, null, 2));
30
+ else
31
+ output.writeln(text);
32
+ return { success: ok, exitCode: ok ? 0 : 1, data };
33
+ }
34
+ export const catalogCommand = {
35
+ name: 'catalog',
36
+ description: 'ADR-406 command inventory and canonical command catalog (generate, verify, engine report)',
37
+ options: [
38
+ { name: 'repo-root', type: 'string', description: 'Repository root (default: cwd)' },
39
+ { name: 'engine', type: 'string', description: 'Claude Code executable to probe (default ~/.local/bin/claude)' },
40
+ { name: 'check', type: 'boolean', description: 'generate: compare with the committed catalog instead of writing' },
41
+ { name: 'file', type: 'string', description: 'verify: catalog file to verify' },
42
+ { name: 'json', type: 'boolean', description: 'Machine-readable output' },
43
+ ],
44
+ examples: [
45
+ { command: 'ruflo catalog generate', description: 'Regenerate the catalog and engine report' },
46
+ { command: 'ruflo catalog generate --check', description: 'Fail if the committed catalog is stale' },
47
+ { command: 'ruflo catalog verify --file catalog.generated.json', description: 'Verify a copied catalog (parity)' },
48
+ ],
49
+ async action(ctx) {
50
+ const flags = ctx.flags;
51
+ const op = ctx.args[0] ?? 'generate';
52
+ const json = flags.json === true;
53
+ const root = resolve(String(flags.repoRoot ?? ctx.cwd ?? process.cwd()));
54
+ try {
55
+ if (op === 'verify') {
56
+ const file = resolve(String(flags.file ?? join(root, CATALOG_RELATIVE_PATH)));
57
+ const result = verifyCatalog(JSON.parse(readFileSync(file, 'utf8')));
58
+ return finish({ file, ...result }, result.valid, json, result.valid ? `catalog valid: ${file}` : `catalog INVALID:\n${result.errors.join('\n')}`);
59
+ }
60
+ if (op !== 'generate' && op !== 'engine') {
61
+ return finish({ error: `unknown operation ${op}` }, false, json, `unknown operation: ${op} (generate | verify | engine)`);
62
+ }
63
+ const result = generate({ repoRoot: root, engineExecutable: engineFrom(flags) });
64
+ if (op === 'engine')
65
+ return finish(result.engine, true, json, JSON.stringify(result.engine, null, 2));
66
+ const catalogPath = join(root, CATALOG_RELATIVE_PATH);
67
+ const summary = summarize(result);
68
+ if (flags.check === true) {
69
+ const committed = existsSync(catalogPath) ? readFileSync(catalogPath, 'utf8') : '';
70
+ const fresh = serializeCatalog(result.catalog);
71
+ const same = committed === fresh;
72
+ return finish({ catalogPath, upToDate: same, summary }, same, json, same ? 'catalog up to date' : `catalog is stale: run \`ruflo catalog generate\` (${catalogPath})`);
73
+ }
74
+ const previous = existsSync(catalogPath) ? sha256Hex(readFileSync(catalogPath)) : null;
75
+ const write = writeCatalog(catalogPath, result.catalog, previous);
76
+ // The engine report is host evidence (engine build, binary digest), kept
77
+ // beside the catalog but outside its digest, which must stay host-free.
78
+ writeFileSync(join(root, ENGINE_REPORT_RELATIVE_PATH), `${JSON.stringify(JSON.parse(canonicalJson(result.engine)), null, 2)}\n`);
79
+ return finish({ catalogPath, written: write.written, sha256: write.sha256, summary }, true, json, `${write.written ? 'wrote' : 'unchanged'} ${catalogPath}\n${JSON.stringify(summary, null, 2)}`);
80
+ }
81
+ catch (error) {
82
+ const message = error instanceof Error ? error.message : String(error);
83
+ output.printError(message);
84
+ return { success: false, exitCode: 1, message };
85
+ }
86
+ },
87
+ };
88
+ export default catalogCommand;
89
+ //# sourceMappingURL=catalog.js.map
@@ -2308,15 +2308,20 @@ async function checkMetaharness() {
2308
2308
  }
2309
2309
  // ADR-404 — ruflo as a Claude Code mod (function hooks, early access). One
2310
2310
  // line in a bare `doctor`: whether the mod is enabled, can load (function
2311
- // hooks on, not refused by allowManagedModsOnly) and has started; never a
2312
- // failure, since the classic hooks are the default and the fallback.
2313
- // `ruflo mods doctor` prints every finding.
2311
+ // hooks on, not refused by allowManagedModsOnly) and has started. Only an
2312
+ // opted-in project whose plugin Claude Code cannot resolve (stale ruflo
2313
+ // marketplace clone, nothing installed) fails: the classic hooks still run,
2314
+ // but the opt-in silently does nothing. `ruflo mods doctor` prints every finding.
2314
2315
  async function checkMods() {
2315
2316
  const { probeMods } = await import('../mods/probe.js');
2316
2317
  const findings = probeMods({ projectRoot: process.cwd() });
2317
2318
  const enabled = findings.find((f) => f.name === 'ruflo-mods plugin')?.status === 'pass';
2318
2319
  if (!enabled)
2319
2320
  return { name: 'ruflo mods (ADR-404)', status: 'pass', message: 'not enabled; classic hooks handle every event' };
2321
+ const failures = findings.filter((f) => f.status === 'fail');
2322
+ if (failures.length > 0) {
2323
+ return { name: 'ruflo mods (ADR-404)', status: 'fail', message: failures.map((f) => `${f.name}: ${f.message}`).join('; '), fix: failures[0].fix ?? 'ruflo mods doctor' };
2324
+ }
2320
2325
  const warnings = findings.filter((f) => f.status !== 'pass');
2321
2326
  return warnings.length === 0
2322
2327
  ? { name: 'ruflo mods (ADR-404)', status: 'pass', message: findings.find((f) => f.name === 'last mod start')?.message ?? 'enabled' }
@@ -90,6 +90,9 @@ const commandLoaders = {
90
90
  spinner: () => import('./spinner.js'),
91
91
  // ruflo as a Claude Code mod: function hooks, early access (ADR-404)
92
92
  mods: () => import('./mods.js'),
93
+ // ADR-406 command catalog and mission control (interim homes until mods.ts follow-up)
94
+ catalog: () => import('./catalog.js'),
95
+ mission: () => import('./mission.js'),
93
96
  // Ruflo entries in Claude Code's companyAnnouncements startup rotation (ADR-319)
94
97
  announcements: () => import('./announcements.js'),
95
98
  // AGNTCY/Outshift runtime transport selection (ADR-324 §2) — optional,
@@ -775,17 +775,18 @@ const initClaudeAction = async (ctx) => {
775
775
  output.writeln(output.warning(' Embedding initialization skipped (run manually)'));
776
776
  }
777
777
  }
778
- // ADR-404 — opt into the Claude Code mod path. Additive: the classic
779
- // hooks just written stay the default and the fallback.
780
- if (ctx.flags.mods === true) {
778
+ // ADR-404 (amended: mods default-on in init) — enable the ruflo mod
779
+ // plugins in the committed .claude/settings.json, then install them with
780
+ // claude. Additive: the classic hooks just written stay the fallback.
781
+ // --no-mods opts out; --no-plugin-install writes settings only.
782
+ if (ctx.flags.mods !== false) {
781
783
  output.writeln();
782
784
  try {
783
- const { installMod } = await import('../mods/install.js');
784
- const installed = installMod(ctx.cwd, 'local');
785
- output.writeln(output.success(` ✓ ruflo-mods enabled in ${installed.settingsFile} (early access; run "ruflo mods doctor")`));
785
+ const { applyMods } = await import('../mods/apply.js');
786
+ await applyMods(ctx.cwd, { scope: 'project', pluginInstall: ctx.flags.pluginInstall !== false && ctx.flags['plugin-install'] !== false, skipInTestEnv: true });
786
787
  }
787
788
  catch (err) {
788
- output.writeln(output.warning(` ruflo-mods not enabled: ${err instanceof Error ? err.message : String(err)}`));
789
+ output.writeln(output.warning(` ruflo mods not enabled: ${err instanceof Error ? err.message : String(err)}`));
789
790
  }
790
791
  }
791
792
  if (!startDaemon && !startAll) {
@@ -1062,6 +1063,17 @@ const wizardCommand = {
1062
1063
  output.printInfo(` ... and ${result.warnings.length - 5} more`);
1063
1064
  }
1064
1065
  }
1066
+ // ADR-404 amendment — the wizard enables the mods as `ruflo init` does.
1067
+ if (ctx.flags.mods !== false) {
1068
+ output.writeln();
1069
+ try {
1070
+ const { applyMods } = await import('../mods/apply.js');
1071
+ await applyMods(ctx.cwd, { scope: 'project', pluginInstall: ctx.flags.pluginInstall !== false && ctx.flags['plugin-install'] !== false, skipInTestEnv: true });
1072
+ }
1073
+ catch (err) {
1074
+ output.writeln(output.warning(` ruflo mods not enabled: ${err instanceof Error ? err.message : String(err)}`));
1075
+ }
1076
+ }
1065
1077
  // Initialize embeddings if enabled
1066
1078
  let embeddingsInitialized = false;
1067
1079
  if (enableEmbeddings) {
@@ -1322,14 +1334,27 @@ const upgradeCommand = {
1322
1334
  {
1323
1335
  name: 'settings',
1324
1336
  short: 's',
1325
- description: 'Merge new settings (Agent Teams, hooks) into existing settings.json',
1337
+ description: 'Merge new settings (Agent Teams, hooks, ruflo mods) into existing settings.json',
1326
1338
  type: 'boolean',
1327
1339
  default: false,
1328
1340
  },
1341
+ {
1342
+ // ADR-404 amendment — the same merge + plugin install as `ruflo init`.
1343
+ name: 'mods',
1344
+ description: 'Merge the ruflo mod plugins into .claude/settings.json and install them (also implied by --add-missing and --settings; --no-mods to skip)',
1345
+ type: 'boolean',
1346
+ },
1347
+ {
1348
+ name: 'plugin-install',
1349
+ description: 'With mods: run claude plugin install (--no-plugin-install to merge settings only)',
1350
+ type: 'boolean',
1351
+ default: true,
1352
+ },
1329
1353
  ],
1330
1354
  action: async (ctx) => {
1331
1355
  const addMissing = (ctx.flags['add-missing'] || ctx.flags.addMissing);
1332
1356
  const upgradeSettings = (ctx.flags.settings);
1357
+ const upgradeMods = ctx.flags.mods === true || (ctx.flags.mods !== false && (addMissing || upgradeSettings));
1333
1358
  output.writeln();
1334
1359
  output.writeln(output.bold('Upgrading RuFlo'));
1335
1360
  if (addMissing && upgradeSettings) {
@@ -1363,6 +1388,14 @@ const upgradeCommand = {
1363
1388
  }
1364
1389
  spinner.succeed('Upgrade complete!');
1365
1390
  output.writeln();
1391
+ // ADR-404 amendment — merge the mod plugins without clobbering: only
1392
+ // keys that are absent are added, and each is reported.
1393
+ if (upgradeMods) {
1394
+ const { applyMods, describeAdded } = await import('../mods/apply.js');
1395
+ const mods = await applyMods(ctx.cwd, { scope: 'project', pluginInstall: ctx.flags.pluginInstall !== false && ctx.flags['plugin-install'] !== false, skipInTestEnv: true });
1396
+ result.settingsUpdated = [...(result.settingsUpdated ?? []), ...describeAdded(mods.install.added)];
1397
+ output.writeln();
1398
+ }
1366
1399
  // Show what was updated
1367
1400
  if (result.updated.length > 0) {
1368
1401
  output.printBox(result.updated.map(f => `✓ ${f}`).join('\n'), 'Updated (latest version)');
@@ -1514,11 +1547,17 @@ export const initCommand = {
1514
1547
  default: false,
1515
1548
  },
1516
1549
  {
1517
- // ADR-404 — Claude Code function hooks are early access; opt-in only.
1550
+ // ADR-404 amendment — on by default; nothing loads where function hooks are off.
1518
1551
  name: 'mods',
1519
- description: 'Also enable the ruflo Claude Code mod (function hooks, early access); classic hooks stay as fallback',
1552
+ description: 'Enable the ruflo mod plugins in .claude/settings.json (default on; --no-mods to skip); classic hooks stay as fallback',
1520
1553
  type: 'boolean',
1521
- default: false,
1554
+ default: true,
1555
+ },
1556
+ {
1557
+ name: 'plugin-install',
1558
+ description: 'With mods: refresh the ruflo marketplace and claude plugin install the mods (--no-plugin-install to write settings only)',
1559
+ type: 'boolean',
1560
+ default: true,
1522
1561
  },
1523
1562
  {
1524
1563
  name: 'with-embeddings',
@@ -0,0 +1,24 @@
1
+ /**
2
+ * `ruflo mission` — ADR-406 §19.7 mission operations from the CLI.
3
+ *
4
+ * mission create --objective <text> [--request-id <id>]
5
+ * mission plan --mission <id> --expected-revision <n> --plan-file <json> [--request-id <id>]
6
+ * mission get [--mission <id>]
7
+ * mission events --mission <id> [--after <seq>] [--limit <n>]
8
+ * mission action --mission <id> --expected-revision <n> --action <requestAuthorization|pause|cancel|admit|resume> [--reason <text>]
9
+ *
10
+ * Flags map one-to-one onto the raw input the MCP tools receive; the shared
11
+ * `MissionService` validates both. Output is JSON. A missing request id gets a
12
+ * fresh one: a new id is a new intentional request (ADR-406 §9); pass the same
13
+ * id to retry the same request safely.
14
+ */
15
+ import type { Command } from '../types.js';
16
+ import type { MissionPort } from '../mcp-tools/mission-tools.js';
17
+ export type MissionPortFactoryCli = (projectRoot: string) => MissionPort;
18
+ /** Test seam (London school): substitute the service the command talks to. */
19
+ export declare function setMissionPortFactory(next: MissionPortFactoryCli | null): void;
20
+ /** Raw operation input from flags; undefined fields are dropped so defaults apply. */
21
+ export declare function inputFromFlags(op: string, flags: Record<string, unknown>, cwd: string): Record<string, unknown>;
22
+ export declare const missionCommand: Command;
23
+ export default missionCommand;
24
+ //# sourceMappingURL=mission.d.ts.map
@@ -0,0 +1,116 @@
1
+ /**
2
+ * `ruflo mission` — ADR-406 §19.7 mission operations from the CLI.
3
+ *
4
+ * mission create --objective <text> [--request-id <id>]
5
+ * mission plan --mission <id> --expected-revision <n> --plan-file <json> [--request-id <id>]
6
+ * mission get [--mission <id>]
7
+ * mission events --mission <id> [--after <seq>] [--limit <n>]
8
+ * mission action --mission <id> --expected-revision <n> --action <requestAuthorization|pause|cancel|admit|resume> [--reason <text>]
9
+ *
10
+ * Flags map one-to-one onto the raw input the MCP tools receive; the shared
11
+ * `MissionService` validates both. Output is JSON. A missing request id gets a
12
+ * fresh one: a new id is a new intentional request (ADR-406 §9); pass the same
13
+ * id to retry the same request safely.
14
+ */
15
+ import { randomUUID } from 'node:crypto';
16
+ import { lstatSync, readFileSync } from 'node:fs';
17
+ import { resolve } from 'node:path';
18
+ import { output } from '../output.js';
19
+ import { MissionService } from '../missions/service.js';
20
+ const PLAN_FILE_MAX_BYTES = 256 * 1024;
21
+ let factory = (projectRoot) => new MissionService({ projectRoot, channel: 'cli' });
22
+ /** Test seam (London school): substitute the service the command talks to. */
23
+ export function setMissionPortFactory(next) {
24
+ factory = next ?? ((projectRoot) => new MissionService({ projectRoot, channel: 'cli' }));
25
+ }
26
+ function int(value) {
27
+ if (value === undefined || value === null || value === '')
28
+ return undefined;
29
+ const n = Number(value);
30
+ return Number.isFinite(n) ? n : Number.NaN;
31
+ }
32
+ function readPlanFile(path) {
33
+ const stat = lstatSync(path);
34
+ if (!stat.isFile())
35
+ throw new Error('plan-file must be a regular file');
36
+ if (stat.size > PLAN_FILE_MAX_BYTES)
37
+ throw new Error(`plan-file exceeds ${PLAN_FILE_MAX_BYTES} bytes`);
38
+ return JSON.parse(readFileSync(path, 'utf8'));
39
+ }
40
+ /** Raw operation input from flags; undefined fields are dropped so defaults apply. */
41
+ export function inputFromFlags(op, flags, cwd) {
42
+ const raw = {};
43
+ const set = (key, value) => { if (value !== undefined)
44
+ raw[key] = value; };
45
+ const requestId = typeof flags.requestId === 'string' ? flags.requestId : randomUUID();
46
+ if (op === 'create') {
47
+ set('requestId', requestId);
48
+ set('objective', flags.objective);
49
+ }
50
+ else if (op === 'plan') {
51
+ set('requestId', requestId);
52
+ set('missionId', flags.mission);
53
+ set('expectedRevision', int(flags.expectedRevision));
54
+ if (typeof flags.planFile === 'string')
55
+ set('plan', readPlanFile(resolve(cwd, flags.planFile)));
56
+ }
57
+ else if (op === 'get') {
58
+ set('missionId', flags.mission);
59
+ }
60
+ else if (op === 'events') {
61
+ set('missionId', flags.mission);
62
+ set('afterSequence', int(flags.after));
63
+ set('limit', int(flags.limit));
64
+ }
65
+ else if (op === 'action') {
66
+ set('requestId', requestId);
67
+ set('missionId', flags.mission);
68
+ set('expectedRevision', int(flags.expectedRevision));
69
+ set('action', flags.action);
70
+ set('reason', flags.reason);
71
+ }
72
+ return raw;
73
+ }
74
+ export const missionCommand = {
75
+ name: 'mission',
76
+ description: 'ADR-406 mission control: create, plan, get, events, action (shared runtime with the mission_* MCP tools)',
77
+ options: [
78
+ { name: 'objective', type: 'string', description: 'create: mission objective' },
79
+ { name: 'mission', type: 'string', description: 'Mission id (msn_…)' },
80
+ { name: 'request-id', type: 'string', description: 'Idempotency key; reuse only to retry the same request' },
81
+ { name: 'expected-revision', type: 'number', description: 'Revision the request was prepared against' },
82
+ { name: 'plan-file', type: 'string', description: 'plan: JSON file with { tasks, acceptance, budget, scope }' },
83
+ { name: 'after', type: 'number', description: 'events: cursor (last sequence seen)' },
84
+ { name: 'limit', type: 'number', description: 'events: page size (max 500)' },
85
+ { name: 'action', type: 'string', description: 'action: requestAuthorization | pause | cancel | admit | resume' },
86
+ { name: 'reason', type: 'string', description: 'action: reason' },
87
+ { name: 'project-root', type: 'string', description: 'Project root (default: cwd)' },
88
+ ],
89
+ examples: [
90
+ { command: 'ruflo mission create --objective "Ship the report" --request-id r1', description: 'Create a draft mission' },
91
+ { command: 'ruflo mission plan --mission msn_… --expected-revision 1 --plan-file plan.json', description: 'Submit a plan' },
92
+ { command: 'ruflo mission events --mission msn_… --after 0', description: 'Read events from a cursor' },
93
+ ],
94
+ async action(ctx) {
95
+ const flags = ctx.flags;
96
+ const op = ctx.args[0] ?? 'get';
97
+ const root = resolve(String(flags.projectRoot ?? ctx.cwd ?? process.cwd()));
98
+ const ops = { create: 'create', plan: 'plan', get: 'get', events: 'events', action: 'requestAction' };
99
+ const method = ops[op];
100
+ if (!method) {
101
+ output.printError(`unknown mission operation: ${op} (create | plan | get | events | action)`);
102
+ return { success: false, exitCode: 1 };
103
+ }
104
+ let result;
105
+ try {
106
+ result = await factory(root)[method](inputFromFlags(op, flags, root));
107
+ }
108
+ catch (error) {
109
+ result = { ok: false, code: 'invalid-input', message: error instanceof Error ? error.message : String(error) };
110
+ }
111
+ output.writeln(JSON.stringify(result, null, 2));
112
+ return { success: result.ok, exitCode: result.ok ? 0 : result.code === 'revision-conflict' ? 3 : 1, data: result };
113
+ },
114
+ };
115
+ export default missionCommand;
116
+ //# sourceMappingURL=mission.js.map
@@ -2,7 +2,7 @@
2
2
  * `ruflo mods` — opt into running ruflo as a Claude Code mod (ADR-404,
3
3
  * Claude Code function hooks, early access).
4
4
  *
5
- * install/uninstall edit one settings file and record what they added;
5
+ * install/uninstall edit settings files and record what they added;
6
6
  * status/doctor report what can be known from outside a session; sync-policy
7
7
  * rewrites the policy projection the mod's tool check reads. Classic hooks
8
8
  * are never removed: they stay the default and the fallback.
@@ -2,14 +2,34 @@
2
2
  * `ruflo mods` — opt into running ruflo as a Claude Code mod (ADR-404,
3
3
  * Claude Code function hooks, early access).
4
4
  *
5
- * install/uninstall edit one settings file and record what they added;
5
+ * install/uninstall edit settings files and record what they added;
6
6
  * status/doctor report what can be known from outside a session; sync-policy
7
7
  * rewrites the policy projection the mod's tool check reads. Classic hooks
8
8
  * are never removed: they stay the default and the fallback.
9
9
  */
10
10
  import { output } from '../output.js';
11
- import { installMod, uninstallMod } from '../mods/install.js';
11
+ import { existsSync, readFileSync } from 'node:fs';
12
+ import { join, resolve } from 'node:path';
13
+ import { directoryMarketplace, MARKETPLACE_NAME } from '../mods/install.js';
12
14
  import { probeMods } from '../mods/probe.js';
15
+ import { applyMods, removeMods } from '../mods/apply.js';
16
+ /**
17
+ * `--source local` / `--marketplace-path <dir>`: a ruflo checkout as a
18
+ * directory marketplace (dogfooding). It must be a ruflo marketplace.
19
+ */
20
+ function localMarketplace(dir) {
21
+ const catalog = join(resolve(dir), '.claude-plugin', 'marketplace.json');
22
+ if (!existsSync(catalog))
23
+ return `${dir} has no .claude-plugin/marketplace.json`;
24
+ try {
25
+ if (JSON.parse(readFileSync(catalog, 'utf8')).name !== MARKETPLACE_NAME)
26
+ return `${catalog} is not the ${MARKETPLACE_NAME} marketplace`;
27
+ }
28
+ catch {
29
+ return `${catalog} is not JSON`;
30
+ }
31
+ return directoryMarketplace(dir);
32
+ }
13
33
  function projectRoot(ctx) {
14
34
  return ctx.flags.projectRoot ?? ctx.flags['project-root'] ?? ctx.cwd ?? process.cwd();
15
35
  }
@@ -24,11 +44,15 @@ function printFindings(findings) {
24
44
  const rootOption = { name: 'project-root', description: 'Project root (default: current directory)', type: 'string' };
25
45
  const installSub = {
26
46
  name: 'install',
27
- description: 'Enable the ruflo-mods plugin for this project (opt-in; classic hooks stay as fallback)',
47
+ description: 'Enable the ruflo mod plugins (ruflo-mods, ruflo-swarm, ruflo-console) for this project; classic hooks stay as fallback',
28
48
  options: [
29
49
  rootOption,
30
50
  { name: 'scope', description: 'local (.claude/settings.local.json, default) | project (.claude/settings.json)', type: 'string', default: 'local' },
31
- { name: 'dry-run', description: 'Show the settings that would be written', type: 'boolean', default: false },
51
+ { name: 'dry-run', description: 'Show the settings that would be written and the claude commands that would run', type: 'boolean', default: false },
52
+ { name: 'plugin-install', description: 'Refresh the ruflo marketplace and run `claude plugin install` (--no-plugin-install to only write settings)', type: 'boolean', default: true },
53
+ { name: 'strict', description: 'Exit 1 when a required plugin could not be made loadable', type: 'boolean', default: false },
54
+ { name: 'source', description: 'github (ruvnet/ruflo, default) | local (a ruflo checkout as a directory marketplace: plugins load live from it)', type: 'string', default: 'github' },
55
+ { name: 'marketplace-path', description: 'With --source local: the ruflo checkout (default: the project root)', type: 'string' },
32
56
  ],
33
57
  action: async (ctx) => {
34
58
  const scope = ctx.flags.scope ?? 'local';
@@ -36,32 +60,49 @@ const installSub = {
36
60
  output.printError(`--scope must be local or project, got ${scope}`);
37
61
  return { success: false, exitCode: 1 };
38
62
  }
39
- const root = projectRoot(ctx);
40
- const result = installMod(root, scope, (ctx.flags.dryRun === true || ctx.flags['dry-run'] === true));
41
- if (result.dryRun) {
42
- output.writeln(`Would write ${result.settingsFile}:`);
43
- output.printJson(result.next);
44
- return { success: true, data: result };
63
+ const source = ctx.flags.source ?? 'github';
64
+ const path = (ctx.flags.marketplacePath ?? ctx.flags['marketplace-path']);
65
+ if (source !== 'github' && source !== 'local') {
66
+ output.printError(`--source must be github or local, got ${source}`);
67
+ return { success: false, exitCode: 1 };
45
68
  }
46
- await syncPolicy(root, true);
47
- output.printSuccess(`ruflo-mods enabled in ${result.settingsFile}${result.backup ? ` (backup: ${result.backup})` : ''}`);
48
- output.writeln('Restart Claude Code, then run /ruflo-mods in a session to see what the mod owns.');
49
- output.writeln(output.dim('Early access: Claude Code loads it only where function hooks are on. Run `ruflo mods doctor`.'));
50
- return { success: true, data: result };
69
+ let marketplace;
70
+ if (source === 'local' || path) {
71
+ const local = localMarketplace(path ?? projectRoot(ctx));
72
+ if (typeof local === 'string') {
73
+ output.printError(`--source local: ${local}`);
74
+ return { success: false, exitCode: 1 };
75
+ }
76
+ marketplace = local;
77
+ }
78
+ const result = await applyMods(projectRoot(ctx), {
79
+ scope: scope,
80
+ marketplace,
81
+ dryRun: ctx.flags.dryRun === true || ctx.flags['dry-run'] === true,
82
+ pluginInstall: ctx.flags.pluginInstall !== false && ctx.flags['plugin-install'] !== false,
83
+ });
84
+ if (result.install.dryRun)
85
+ return { success: true, data: result };
86
+ if (result.resolvable)
87
+ output.writeln('Restart Claude Code, then run /ruflo in a session to open the console (/ruflo mods shows what the mod owns). Check with: ruflo mods doctor');
88
+ const data = { ...result.install, resolvable: result.resolvable };
89
+ return result.resolvable || ctx.flags.strict !== true ? { success: true, data } : { success: false, exitCode: 1, data };
51
90
  },
52
91
  };
53
92
  const uninstallSub = {
54
93
  name: 'uninstall',
55
- description: 'Remove what `ruflo mods install` added (classic hooks take every event back)',
94
+ description: 'Remove what `ruflo mods install` added, and claude plugin uninstall what it installed (classic hooks take every event back)',
56
95
  options: [rootOption, { name: 'dry-run', description: 'Show what would be removed', type: 'boolean', default: false }],
57
96
  action: async (ctx) => {
58
- const result = uninstallMod(projectRoot(ctx), (ctx.flags.dryRun === true || ctx.flags['dry-run'] === true));
97
+ const result = await removeMods(projectRoot(ctx), { dryRun: ctx.flags.dryRun === true || ctx.flags['dry-run'] === true });
59
98
  if (!result.removed) {
60
99
  output.printWarning('No install record (.claude-flow/mods/install.json): nothing ruflo added to remove.');
61
100
  return { success: true, data: result };
62
101
  }
63
- output.printSuccess(`${result.dryRun ? 'Would remove' : 'Removed'} ruflo-mods from ${result.settingsFile}`);
64
- return { success: true, data: result };
102
+ output.printSuccess(`${result.dryRun ? 'Would remove' : 'Removed'} what ruflo mods added from ${result.settingsFiles.join(', ')}`);
103
+ if (result.uninstalled.length)
104
+ output.writeln(`claude plugin uninstall: ${result.uninstalled.join(', ')}`);
105
+ return { success: result.failed.length === 0, exitCode: result.failed.length ? 1 : 0, data: result };
65
106
  },
66
107
  };
67
108
  function findingsCommand(name, description) {
@@ -76,8 +117,8 @@ function findingsCommand(name, description) {
76
117
  else
77
118
  printFindings(findings);
78
119
  const failed = findings.some((f) => f.status === 'fail');
79
- // status reports; doctor gates (warnings are expected while early access is off).
80
- return { success: !failed, exitCode: name === 'doctor' && failed ? 1 : 0, data: findings };
120
+ // status reports (exit 0); doctor gates on a failure (warnings are expected while early access is off).
121
+ return name === 'doctor' && failed ? { success: false, exitCode: 1, data: findings } : { success: true, data: findings };
81
122
  },
82
123
  };
83
124
  }
@@ -116,8 +157,11 @@ export const modsCommand = {
116
157
  syncPolicySub,
117
158
  ],
118
159
  examples: [
119
- { command: 'ruflo mods install', description: 'Enable for this project (settings.local.json)' },
120
- { command: 'ruflo mods doctor', description: 'Function hooks on? Refused by policy? Handshake supported?' },
160
+ { command: 'ruflo mods install', description: 'Enable the mod plugins for this checkout (settings.local.json) and install them with claude' },
161
+ { command: 'ruflo mods install --scope project', description: 'Same, in the committed settings.json (what ruflo init does)' },
162
+ { command: 'ruflo mods install --no-plugin-install', description: 'Only write settings; run no claude command' },
163
+ { command: 'ruflo mods install --source local', description: 'Dogfood: load the mods live from this ruflo checkout (directory marketplace)' },
164
+ { command: 'ruflo mods doctor', description: 'Marketplace fresh? Plugins loadable? Function hooks on? Refused by policy?' },
121
165
  { command: 'ruflo mods uninstall', description: 'Remove only what install added' },
122
166
  ],
123
167
  action: statusSub.action,