claude-flow 3.49.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 (66) hide show
  1. package/.claude/helpers/hook-handler.cjs +14 -1
  2. package/.claude/proven-config.json +1 -1
  3. package/.claude/settings.json +11 -2
  4. package/.claude-plugin/marketplace.json +15 -0
  5. package/package.json +1 -1
  6. package/v3/@claude-flow/cli/README.md +0 -2
  7. package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
  8. package/v3/@claude-flow/cli/dist/src/commands/catalog.d.ts +18 -0
  9. package/v3/@claude-flow/cli/dist/src/commands/catalog.js +89 -0
  10. package/v3/@claude-flow/cli/dist/src/commands/doctor.js +24 -1
  11. package/v3/@claude-flow/cli/dist/src/commands/index.js +5 -0
  12. package/v3/@claude-flow/cli/dist/src/commands/init.js +60 -1
  13. package/v3/@claude-flow/cli/dist/src/commands/mission.d.ts +24 -0
  14. package/v3/@claude-flow/cli/dist/src/commands/mission.js +116 -0
  15. package/v3/@claude-flow/cli/dist/src/commands/mods.d.ts +13 -0
  16. package/v3/@claude-flow/cli/dist/src/commands/mods.js +170 -0
  17. package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +12 -0
  18. package/v3/@claude-flow/cli/dist/src/mcp-client.js +3 -0
  19. package/v3/@claude-flow/cli/dist/src/mcp-tools/capability-brain.js +1 -1
  20. package/v3/@claude-flow/cli/dist/src/mcp-tools/index.d.ts +1 -0
  21. package/v3/@claude-flow/cli/dist/src/mcp-tools/index.js +2 -0
  22. package/v3/@claude-flow/cli/dist/src/mcp-tools/mission-tools.d.ts +22 -0
  23. package/v3/@claude-flow/cli/dist/src/mcp-tools/mission-tools.js +75 -0
  24. package/v3/@claude-flow/cli/dist/src/missions/fold.d.ts +19 -0
  25. package/v3/@claude-flow/cli/dist/src/missions/fold.js +238 -0
  26. package/v3/@claude-flow/cli/dist/src/missions/index.d.ts +10 -0
  27. package/v3/@claude-flow/cli/dist/src/missions/index.js +10 -0
  28. package/v3/@claude-flow/cli/dist/src/missions/observation.d.ts +70 -0
  29. package/v3/@claude-flow/cli/dist/src/missions/observation.js +77 -0
  30. package/v3/@claude-flow/cli/dist/src/missions/schemas.d.ts +735 -0
  31. package/v3/@claude-flow/cli/dist/src/missions/schemas.js +150 -0
  32. package/v3/@claude-flow/cli/dist/src/missions/service.d.ts +88 -0
  33. package/v3/@claude-flow/cli/dist/src/missions/service.js +278 -0
  34. package/v3/@claude-flow/cli/dist/src/missions/store.d.ts +68 -0
  35. package/v3/@claude-flow/cli/dist/src/missions/store.js +209 -0
  36. package/v3/@claude-flow/cli/dist/src/missions/transitions.d.ts +81 -0
  37. package/v3/@claude-flow/cli/dist/src/missions/transitions.js +178 -0
  38. package/v3/@claude-flow/cli/dist/src/mods/apply.d.ts +50 -0
  39. package/v3/@claude-flow/cli/dist/src/mods/apply.js +186 -0
  40. package/v3/@claude-flow/cli/dist/src/mods/claude-installs.d.ts +29 -0
  41. package/v3/@claude-flow/cli/dist/src/mods/claude-installs.js +83 -0
  42. package/v3/@claude-flow/cli/dist/src/mods/command-registry/catalog.d.ts +45 -0
  43. package/v3/@claude-flow/cli/dist/src/mods/command-registry/catalog.js +234 -0
  44. package/v3/@claude-flow/cli/dist/src/mods/command-registry/engine-report.d.ts +66 -0
  45. package/v3/@claude-flow/cli/dist/src/mods/command-registry/engine-report.js +119 -0
  46. package/v3/@claude-flow/cli/dist/src/mods/command-registry/generate.d.ts +51 -0
  47. package/v3/@claude-flow/cli/dist/src/mods/command-registry/generate.js +166 -0
  48. package/v3/@claude-flow/cli/dist/src/mods/command-registry/index.d.ts +9 -0
  49. package/v3/@claude-flow/cli/dist/src/mods/command-registry/index.js +9 -0
  50. package/v3/@claude-flow/cli/dist/src/mods/command-registry/inventory.d.ts +63 -0
  51. package/v3/@claude-flow/cli/dist/src/mods/command-registry/inventory.js +230 -0
  52. package/v3/@claude-flow/cli/dist/src/mods/command-registry/schema.d.ts +836 -0
  53. package/v3/@claude-flow/cli/dist/src/mods/command-registry/schema.js +137 -0
  54. package/v3/@claude-flow/cli/dist/src/mods/install.d.ts +102 -0
  55. package/v3/@claude-flow/cli/dist/src/mods/install.js +185 -0
  56. package/v3/@claude-flow/cli/dist/src/mods/plugin-repair.d.ts +81 -0
  57. package/v3/@claude-flow/cli/dist/src/mods/plugin-repair.js +120 -0
  58. package/v3/@claude-flow/cli/dist/src/mods/plugin-resolve.d.ts +63 -0
  59. package/v3/@claude-flow/cli/dist/src/mods/plugin-resolve.js +182 -0
  60. package/v3/@claude-flow/cli/dist/src/mods/policy-projection.d.ts +39 -0
  61. package/v3/@claude-flow/cli/dist/src/mods/policy-projection.js +65 -0
  62. package/v3/@claude-flow/cli/dist/src/mods/probe.d.ts +33 -0
  63. package/v3/@claude-flow/cli/dist/src/mods/probe.js +143 -0
  64. package/v3/@claude-flow/cli/dist/src/services/policy-runtime.d.ts +5 -0
  65. package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +20 -1
  66. package/v3/@claude-flow/cli/package.json +1 -1
@@ -286,6 +286,17 @@ async function readStdin() {
286
286
  });
287
287
  }
288
288
 
289
+ // ADR-404: the ruflo mod (plugins/ruflo-mods) runs these events in-process
290
+ // inside Claude Code and sets RUFLO_MODS_OWNS on the process, which every hook
291
+ // started after inherits. An event named there is the mod's; returning here
292
+ // keeps it from firing twice. Only side-effect events can be handed over:
293
+ // guards such as pre-bash always run, whatever the variable says.
294
+ const MOD_OWNABLE_EVENTS = new Set(['route', 'post-edit']);
295
+ function ownedByMod(cmd, env = process.env) {
296
+ if (!MOD_OWNABLE_EVENTS.has(cmd)) return false;
297
+ return String(env.RUFLO_MODS_OWNS || '').split(',').some((owned) => owned.trim() === cmd);
298
+ }
299
+
289
300
  function claimSideEffectEvent(family, stdinData, event) {
290
301
  if (/^(1|true|yes|on)$/i.test(process.env.RUFLO_DISABLE_HOOK_DEDUP || '')) return true;
291
302
  try {
@@ -312,6 +323,8 @@ function claimSideEffectEvent(family, stdinData, event) {
312
323
  }
313
324
 
314
325
  async function main() {
326
+ if (ownedByMod(command)) return;
327
+
315
328
  // Global safety timeout: hooks must NEVER hang (#1530, #1531)
316
329
  const safetyTimer = setTimeout(() => {
317
330
  process.stderr.write("[WARN] Hook handler global timeout (5s), forcing exit\n");
@@ -606,4 +619,4 @@ if (require.main === module) {
606
619
  // Which sibling helpers loaded (all CommonJS, shipped as .cjs — #3555).
607
620
  const loadedHelpers = { router: !!router, session: !!session, memory: !!memory, intelligence: !!intelligence };
608
621
 
609
- module.exports = { runWithTimeout, INTELLIGENCE_TIMEOUT_MS, loadedHelpers };
622
+ module.exports = { runWithTimeout, INTELLIGENCE_TIMEOUT_MS, loadedHelpers, ownedByMod, MOD_OWNABLE_EVENTS };
@@ -1,5 +1,5 @@
1
1
  {
2
- "adoptedAt": 1790812079679,
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
  }
@@ -215,6 +215,21 @@
215
215
  "name": "ruflo-music",
216
216
  "source": "./plugins/ruflo-music",
217
217
  "description": "AI music generation via Cognitum Music (cogmusic MCP) \u2014 compose lyrics/prompts, generate tracks, separate stems, extract MIDI, and master through your own music.cognitum.one account"
218
+ },
219
+ {
220
+ "name": "ruflo-mods",
221
+ "source": "./plugins/ruflo-mods",
222
+ "description": "Early access (ADR-404): ruflo as a Claude Code mod (function hooks) \u2014 in-process routing, edit learning signals, tighten-only tool checks from ruflo policy, and the cost budget ladder. Opt-in via `ruflo mods install`; classic hooks stay the fallback"
223
+ },
224
+ {
225
+ "name": "ruflo-ruos",
226
+ "source": "./plugins/ruflo-ruos",
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"
218
233
  }
219
234
  ]
220
235
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "3.49.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-01T00:04:08.841Z",
5
- "gitSha": "0bd8e314",
3
+ "generation": 6,
4
+ "generatedAt": "2026-09-17T21:28:47.530Z",
5
+ "gitSha": "702d1461",
6
6
  "catalog": {
7
- "agents": 175,
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
@@ -2306,6 +2306,27 @@ async function checkMetaharness() {
2306
2306
  };
2307
2307
  }
2308
2308
  }
2309
+ // ADR-404 — ruflo as a Claude Code mod (function hooks, early access). One
2310
+ // line in a bare `doctor`: whether the mod is enabled, can load (function
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.
2315
+ async function checkMods() {
2316
+ const { probeMods } = await import('../mods/probe.js');
2317
+ const findings = probeMods({ projectRoot: process.cwd() });
2318
+ const enabled = findings.find((f) => f.name === 'ruflo-mods plugin')?.status === 'pass';
2319
+ if (!enabled)
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
+ }
2325
+ const warnings = findings.filter((f) => f.status !== 'pass');
2326
+ return warnings.length === 0
2327
+ ? { name: 'ruflo mods (ADR-404)', status: 'pass', message: findings.find((f) => f.name === 'last mod start')?.message ?? 'enabled' }
2328
+ : { name: 'ruflo mods (ADR-404)', status: 'warn', message: warnings.map((f) => `${f.name}: ${f.message}`).join('; '), fix: 'ruflo mods doctor' };
2329
+ }
2309
2330
  // Opt-in @ruvector/typesafe task router (optional peer). `--component typesafe` only.
2310
2331
  async function checkTypesafeRouter() {
2311
2332
  const name = '@ruvector/typesafe router';
@@ -2519,7 +2540,7 @@ export const doctorCommand = {
2519
2540
  {
2520
2541
  name: 'component',
2521
2542
  short: 'c',
2522
- description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, mcp-overhead, claude, browser, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, auth, typesafe, metaharness)',
2543
+ description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, mcp-overhead, claude, browser, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, auth, typesafe, mods, metaharness)',
2523
2544
  type: 'string'
2524
2545
  },
2525
2546
  {
@@ -2661,6 +2682,7 @@ export const doctorCommand = {
2661
2682
  checkFunnel, // ADR-305 — effective funnel state + deciding precedence source
2662
2683
  checkProxySponsoredConsent, // ADR-313 — Meta LLM Proxy sponsored-downtime health
2663
2684
  checkAuth, // ADR-306 — Cognitum identity (warn-only; never fails bare `ruflo doctor`)
2685
+ checkMods, // ADR-404 — Claude Code mod path (warn-only)
2664
2686
  ];
2665
2687
  // #2677: `--component memory` now runs the whole memory-health suite,
2666
2688
  // not just the existence check. Values can be a single check or an
@@ -2714,6 +2736,7 @@ export const doctorCommand = {
2714
2736
  'proxy': [checkProxySponsoredConsent, checkProxyBinary, checkProxyProcess, checkProxyBindAddress],
2715
2737
  'auth': checkAuth, // ADR-306
2716
2738
  'typesafe': checkTypesafeRouter, // opt-in @ruvector/typesafe task router
2739
+ 'mods': checkMods, // ADR-404 — ruflo as a Claude Code mod
2717
2740
  };
2718
2741
  let checksToRun = allChecks;
2719
2742
  if (component && componentMap[component]) {
@@ -88,6 +88,11 @@ const commandLoaders = {
88
88
  advisor: () => import('./advisor.js'),
89
89
  // Ruflo verbs in Claude Code's spinnerVerbs rotation (ADR-318)
90
90
  spinner: () => import('./spinner.js'),
91
+ // ruflo as a Claude Code mod: function hooks, early access (ADR-404)
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'),
91
96
  // Ruflo entries in Claude Code's companyAnnouncements startup rotation (ADR-319)
92
97
  announcements: () => import('./announcements.js'),
93
98
  // AGNTCY/Outshift runtime transport selection (ADR-324 §2) — optional,
@@ -775,6 +775,20 @@ const initClaudeAction = async (ctx) => {
775
775
  output.writeln(output.warning(' Embedding initialization skipped (run manually)'));
776
776
  }
777
777
  }
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) {
783
+ output.writeln();
784
+ try {
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 });
787
+ }
788
+ catch (err) {
789
+ output.writeln(output.warning(` ruflo mods not enabled: ${err instanceof Error ? err.message : String(err)}`));
790
+ }
791
+ }
778
792
  if (!startDaemon && !startAll) {
779
793
  const bin = (process.argv[1] || '').includes('ruflo') ? 'ruflo' : 'claude-flow';
780
794
  output.writeln(output.bold('Next steps:'));
@@ -1049,6 +1063,17 @@ const wizardCommand = {
1049
1063
  output.printInfo(` ... and ${result.warnings.length - 5} more`);
1050
1064
  }
1051
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
+ }
1052
1077
  // Initialize embeddings if enabled
1053
1078
  let embeddingsInitialized = false;
1054
1079
  if (enableEmbeddings) {
@@ -1309,14 +1334,27 @@ const upgradeCommand = {
1309
1334
  {
1310
1335
  name: 'settings',
1311
1336
  short: 's',
1312
- 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',
1313
1338
  type: 'boolean',
1314
1339
  default: false,
1315
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
+ },
1316
1353
  ],
1317
1354
  action: async (ctx) => {
1318
1355
  const addMissing = (ctx.flags['add-missing'] || ctx.flags.addMissing);
1319
1356
  const upgradeSettings = (ctx.flags.settings);
1357
+ const upgradeMods = ctx.flags.mods === true || (ctx.flags.mods !== false && (addMissing || upgradeSettings));
1320
1358
  output.writeln();
1321
1359
  output.writeln(output.bold('Upgrading RuFlo'));
1322
1360
  if (addMissing && upgradeSettings) {
@@ -1350,6 +1388,14 @@ const upgradeCommand = {
1350
1388
  }
1351
1389
  spinner.succeed('Upgrade complete!');
1352
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
+ }
1353
1399
  // Show what was updated
1354
1400
  if (result.updated.length > 0) {
1355
1401
  output.printBox(result.updated.map(f => `✓ ${f}`).join('\n'), 'Updated (latest version)');
@@ -1500,6 +1546,19 @@ export const initCommand = {
1500
1546
  type: 'boolean',
1501
1547
  default: false,
1502
1548
  },
1549
+ {
1550
+ // ADR-404 amendment — on by default; nothing loads where function hooks are off.
1551
+ name: 'mods',
1552
+ description: 'Enable the ruflo mod plugins in .claude/settings.json (default on; --no-mods to skip); classic hooks stay as fallback',
1553
+ type: 'boolean',
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,
1561
+ },
1503
1562
  {
1504
1563
  name: 'with-embeddings',
1505
1564
  description: 'Initialize ONNX embedding subsystem with hyperbolic support',
@@ -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
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `ruflo mods` — opt into running ruflo as a Claude Code mod (ADR-404,
3
+ * Claude Code function hooks, early access).
4
+ *
5
+ * install/uninstall edit settings files and record what they added;
6
+ * status/doctor report what can be known from outside a session; sync-policy
7
+ * rewrites the policy projection the mod's tool check reads. Classic hooks
8
+ * are never removed: they stay the default and the fallback.
9
+ */
10
+ import type { Command } from '../types.js';
11
+ export declare const modsCommand: Command;
12
+ export default modsCommand;
13
+ //# sourceMappingURL=mods.d.ts.map