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.
- package/.claude/helpers/hook-handler.cjs +14 -1
- package/.claude/proven-config.json +1 -1
- package/.claude/settings.json +11 -2
- package/.claude-plugin/marketplace.json +15 -0
- package/package.json +1 -1
- package/v3/@claude-flow/cli/README.md +0 -2
- package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
- package/v3/@claude-flow/cli/dist/src/commands/catalog.d.ts +18 -0
- package/v3/@claude-flow/cli/dist/src/commands/catalog.js +89 -0
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +24 -1
- package/v3/@claude-flow/cli/dist/src/commands/index.js +5 -0
- package/v3/@claude-flow/cli/dist/src/commands/init.js +60 -1
- package/v3/@claude-flow/cli/dist/src/commands/mission.d.ts +24 -0
- package/v3/@claude-flow/cli/dist/src/commands/mission.js +116 -0
- package/v3/@claude-flow/cli/dist/src/commands/mods.d.ts +13 -0
- package/v3/@claude-flow/cli/dist/src/commands/mods.js +170 -0
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +12 -0
- package/v3/@claude-flow/cli/dist/src/mcp-client.js +3 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/capability-brain.js +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/index.d.ts +1 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/mission-tools.d.ts +22 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/mission-tools.js +75 -0
- package/v3/@claude-flow/cli/dist/src/missions/fold.d.ts +19 -0
- package/v3/@claude-flow/cli/dist/src/missions/fold.js +238 -0
- package/v3/@claude-flow/cli/dist/src/missions/index.d.ts +10 -0
- package/v3/@claude-flow/cli/dist/src/missions/index.js +10 -0
- package/v3/@claude-flow/cli/dist/src/missions/observation.d.ts +70 -0
- package/v3/@claude-flow/cli/dist/src/missions/observation.js +77 -0
- package/v3/@claude-flow/cli/dist/src/missions/schemas.d.ts +735 -0
- package/v3/@claude-flow/cli/dist/src/missions/schemas.js +150 -0
- package/v3/@claude-flow/cli/dist/src/missions/service.d.ts +88 -0
- package/v3/@claude-flow/cli/dist/src/missions/service.js +278 -0
- package/v3/@claude-flow/cli/dist/src/missions/store.d.ts +68 -0
- package/v3/@claude-flow/cli/dist/src/missions/store.js +209 -0
- package/v3/@claude-flow/cli/dist/src/missions/transitions.d.ts +81 -0
- package/v3/@claude-flow/cli/dist/src/missions/transitions.js +178 -0
- package/v3/@claude-flow/cli/dist/src/mods/apply.d.ts +50 -0
- package/v3/@claude-flow/cli/dist/src/mods/apply.js +186 -0
- package/v3/@claude-flow/cli/dist/src/mods/claude-installs.d.ts +29 -0
- package/v3/@claude-flow/cli/dist/src/mods/claude-installs.js +83 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/catalog.d.ts +45 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/catalog.js +234 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/engine-report.d.ts +66 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/engine-report.js +119 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/generate.d.ts +51 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/generate.js +166 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/index.d.ts +9 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/index.js +9 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/inventory.d.ts +63 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/inventory.js +230 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/schema.d.ts +836 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/schema.js +137 -0
- package/v3/@claude-flow/cli/dist/src/mods/install.d.ts +102 -0
- package/v3/@claude-flow/cli/dist/src/mods/install.js +185 -0
- package/v3/@claude-flow/cli/dist/src/mods/plugin-repair.d.ts +81 -0
- package/v3/@claude-flow/cli/dist/src/mods/plugin-repair.js +120 -0
- package/v3/@claude-flow/cli/dist/src/mods/plugin-resolve.d.ts +63 -0
- package/v3/@claude-flow/cli/dist/src/mods/plugin-resolve.js +182 -0
- package/v3/@claude-flow/cli/dist/src/mods/policy-projection.d.ts +39 -0
- package/v3/@claude-flow/cli/dist/src/mods/policy-projection.js +65 -0
- package/v3/@claude-flow/cli/dist/src/mods/probe.d.ts +33 -0
- package/v3/@claude-flow/cli/dist/src/mods/probe.js +143 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.d.ts +5 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +20 -1
- 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 };
|
package/.claude/settings.json
CHANGED
|
@@ -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
|
@@ -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
|
[](docs/ruflo-explained.md)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generation":
|
|
4
|
-
"generatedAt": "2026-
|
|
5
|
-
"gitSha": "
|
|
3
|
+
"generation": 6,
|
|
4
|
+
"generatedAt": "2026-09-17T21:28:47.530Z",
|
|
5
|
+
"gitSha": "702d1461",
|
|
6
6
|
"catalog": {
|
|
7
|
-
"agents":
|
|
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
|