claude-flow 3.6.19 → 3.6.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "3.6.19",
3
+ "version": "3.6.21",
4
4
  "description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -43,6 +43,8 @@ const commandLoaders = {
43
43
  // P0 Commands
44
44
  completions: () => import('./completions.js'),
45
45
  doctor: () => import('./doctor.js'),
46
+ // Verification (ADR-095, signed witness manifest)
47
+ verify: () => import('./verify.js'),
46
48
  // Analysis Commands
47
49
  analyze: () => import('./analyze.js'),
48
50
  // Q-Learning Routing Commands
@@ -0,0 +1,19 @@
1
+ /**
2
+ * V3 CLI Verify Command
3
+ *
4
+ * Fetches the verification.md.json witness manifest from the live repo,
5
+ * recomputes SHA-256 of every cited file in the user's installed
6
+ * artifact, re-derives the Ed25519 public key from the manifest's git
7
+ * commit, and verifies the signature.
8
+ *
9
+ * Run via: ruflo verify [--branch <branch>] [--manifest <local-path>]
10
+ *
11
+ * If everything checks, the user has byte-for-byte the same fix
12
+ * footprint as the manifest claims. If anything mismatches, the
13
+ * command exits non-zero and prints which fix regressed or which file
14
+ * drifted.
15
+ */
16
+ import type { Command } from '../types.js';
17
+ export declare const verifyCommand: Command;
18
+ export default verifyCommand;
19
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1,240 @@
1
+ /**
2
+ * V3 CLI Verify Command
3
+ *
4
+ * Fetches the verification.md.json witness manifest from the live repo,
5
+ * recomputes SHA-256 of every cited file in the user's installed
6
+ * artifact, re-derives the Ed25519 public key from the manifest's git
7
+ * commit, and verifies the signature.
8
+ *
9
+ * Run via: ruflo verify [--branch <branch>] [--manifest <local-path>]
10
+ *
11
+ * If everything checks, the user has byte-for-byte the same fix
12
+ * footprint as the manifest claims. If anything mismatches, the
13
+ * command exits non-zero and prints which fix regressed or which file
14
+ * drifted.
15
+ */
16
+ import { createHash } from 'crypto';
17
+ import { existsSync, readFileSync } from 'fs';
18
+ import { dirname, join, sep } from 'path';
19
+ import { fileURLToPath } from 'url';
20
+ import { output } from '../output.js';
21
+ const DEFAULT_MANIFEST_URL = 'https://raw.githubusercontent.com/ruvnet/ruflo/{branch}/verification.md.json';
22
+ async function fetchWitness(branch) {
23
+ const url = DEFAULT_MANIFEST_URL.replace('{branch}', branch);
24
+ const res = await fetch(url);
25
+ if (!res.ok) {
26
+ throw new Error(`Failed to fetch manifest from ${url}: ${res.status} ${res.statusText}`);
27
+ }
28
+ return await res.json();
29
+ }
30
+ function loadLocalWitness(localPath) {
31
+ if (!existsSync(localPath)) {
32
+ throw new Error(`Manifest not found: ${localPath}`);
33
+ }
34
+ return JSON.parse(readFileSync(localPath, 'utf-8'));
35
+ }
36
+ /**
37
+ * Locate the user's installed package root.
38
+ *
39
+ * The witness manifest paths are repo-relative (e.g.
40
+ * "v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js"). For
41
+ * end users, only the dist/ subtree ships in node_modules. We map
42
+ * the repo path → the installed equivalent by stripping the
43
+ * "v3/@claude-flow/<pkg>/" prefix and looking up node_modules/<pkg>/.
44
+ */
45
+ function repoPathToInstalledPath(repoPath) {
46
+ // Match v3/@claude-flow/<pkg>/<rest>
47
+ const match = repoPath.match(/^v3\/(@claude-flow\/[^/]+)\/(.+)$/);
48
+ if (match) {
49
+ const pkg = match[1];
50
+ const rest = match[2];
51
+ // Try several anchors: cwd/node_modules, the dirname of this script's package
52
+ const candidates = [];
53
+ candidates.push(join(process.cwd(), 'node_modules', pkg, rest));
54
+ try {
55
+ const __filename = fileURLToPath(import.meta.url);
56
+ // Walk up looking for node_modules
57
+ let dir = dirname(__filename);
58
+ for (let i = 0; i < 10; i++) {
59
+ candidates.push(join(dir, 'node_modules', pkg, rest));
60
+ const parent = dirname(dir);
61
+ if (parent === dir)
62
+ break;
63
+ dir = parent;
64
+ }
65
+ }
66
+ catch { /* ignore */ }
67
+ for (const c of candidates) {
68
+ if (existsSync(c))
69
+ return c;
70
+ }
71
+ return null;
72
+ }
73
+ // Top-level paths (e.g. package.json) — return relative to cwd
74
+ const top = join(process.cwd(), repoPath);
75
+ return existsSync(top) ? top : null;
76
+ }
77
+ function fileSha256(path) {
78
+ return createHash('sha256').update(readFileSync(path)).digest('hex');
79
+ }
80
+ function fileContains(path, marker) {
81
+ return readFileSync(path, 'utf-8').includes(marker);
82
+ }
83
+ async function verifySignature(witness) {
84
+ // Lazy-load @noble/ed25519 — keep verify command snappy when no signature check needed
85
+ let ed = null;
86
+ try {
87
+ ed = await import('@noble/ed25519');
88
+ }
89
+ catch {
90
+ return { manifestHashOk: false, publicKeyReproducible: false, signatureValid: false };
91
+ }
92
+ // Configure sync sha512 for the v2 API
93
+ const sha512Sync = (...m) => {
94
+ const h = createHash('sha512');
95
+ for (const x of m)
96
+ h.update(x);
97
+ return h.digest();
98
+ };
99
+ ed.etc.sha512Sync = sha512Sync;
100
+ const manifestCanonical = JSON.stringify(witness.manifest);
101
+ const recomputedHash = createHash('sha256').update(manifestCanonical).digest('hex');
102
+ const manifestHashOk = recomputedHash === witness.integrity.manifestHash;
103
+ const seed = createHash('sha256').update(witness.manifest.gitCommit + ':ruflo-witness/v1').digest();
104
+ const reKey = ed.getPublicKey(seed);
105
+ const publicKeyReproducible = Buffer.from(reKey).toString('hex') === witness.integrity.publicKey;
106
+ const signatureValid = ed.verify(Buffer.from(witness.integrity.signature, 'hex'), Buffer.from(witness.integrity.manifestHash, 'hex'), Buffer.from(witness.integrity.publicKey, 'hex'));
107
+ return { manifestHashOk, publicKeyReproducible, signatureValid };
108
+ }
109
+ export const verifyCommand = {
110
+ name: 'verify',
111
+ description: 'Verify installed artifact against the signed witness manifest',
112
+ options: [
113
+ {
114
+ name: 'branch',
115
+ short: 'b',
116
+ type: 'string',
117
+ description: 'Git branch to fetch verification.md.json from (defaults to fix/issues-may-1-3)',
118
+ default: 'fix/issues-may-1-3',
119
+ },
120
+ {
121
+ name: 'manifest',
122
+ short: 'm',
123
+ type: 'string',
124
+ description: 'Local path to a verification.md.json file (overrides --branch)',
125
+ },
126
+ {
127
+ name: 'json',
128
+ type: 'boolean',
129
+ description: 'Output JSON instead of human-readable table',
130
+ default: false,
131
+ },
132
+ ],
133
+ examples: [
134
+ { command: 'ruflo verify', description: 'Fetch latest manifest from main branch + verify' },
135
+ { command: 'ruflo verify --branch main', description: 'Verify against a specific branch' },
136
+ { command: 'ruflo verify --manifest ./verification.md.json', description: 'Use a local manifest copy' },
137
+ { command: 'ruflo verify --json', description: 'Machine-readable output for CI' },
138
+ ],
139
+ action: async (ctx) => {
140
+ const branch = ctx.flags.branch || 'fix/issues-may-1-3';
141
+ const localPath = ctx.flags.manifest;
142
+ const asJson = ctx.flags.json === true;
143
+ if (!asJson) {
144
+ output.writeln();
145
+ output.writeln(output.bold('Ruflo Verification'));
146
+ output.writeln(output.dim('─'.repeat(50)));
147
+ }
148
+ let witness;
149
+ try {
150
+ witness = localPath ? loadLocalWitness(localPath) : await fetchWitness(branch);
151
+ }
152
+ catch (err) {
153
+ const msg = err instanceof Error ? err.message : String(err);
154
+ if (asJson)
155
+ output.printJson({ ok: false, error: msg });
156
+ else
157
+ output.printError(`Could not load witness manifest: ${msg}`);
158
+ return { success: false, exitCode: 1 };
159
+ }
160
+ // Signature verification
161
+ const sig = await verifySignature(witness);
162
+ // File verification
163
+ const fileResults = witness.manifest.fixes.map((fix) => {
164
+ const installedPath = repoPathToInstalledPath(fix.file);
165
+ if (!installedPath) {
166
+ return { ...fix, status: 'missing', sha256Match: false, markerPresent: false, installedPath: null, localSha256: undefined };
167
+ }
168
+ const localHash = fileSha256(installedPath);
169
+ const markerPresent = fileContains(installedPath, fix.marker);
170
+ const sha256Match = localHash === fix.sha256;
171
+ const status = sha256Match && markerPresent
172
+ ? 'pass'
173
+ : (markerPresent ? 'drift' : 'regressed');
174
+ return { ...fix, status, sha256Match, markerPresent, localSha256: localHash, installedPath: installedPath.replace(process.cwd() + sep, '') };
175
+ });
176
+ const passCount = fileResults.filter((r) => r.status === 'pass').length;
177
+ const driftCount = fileResults.filter((r) => r.status === 'drift').length;
178
+ const regressedCount = fileResults.filter((r) => r.status === 'regressed').length;
179
+ const missingCount = fileResults.filter((r) => r.status === 'missing').length;
180
+ const allOk = sig.manifestHashOk && sig.publicKeyReproducible && sig.signatureValid && regressedCount === 0;
181
+ if (asJson) {
182
+ output.printJson({
183
+ ok: allOk,
184
+ manifest: witness.manifest,
185
+ signature: sig,
186
+ results: fileResults,
187
+ summary: { pass: passCount, drift: driftCount, regressed: regressedCount, missing: missingCount },
188
+ });
189
+ return { success: allOk, exitCode: allOk ? 0 : 1 };
190
+ }
191
+ output.writeln();
192
+ output.writeln(output.bold('Manifest signature'));
193
+ output.writeln(` manifest hash matches: ${sig.manifestHashOk ? output.success('yes') : output.error('no')}`);
194
+ output.writeln(` public key reproducible from gitCommit: ${sig.publicKeyReproducible ? output.success('yes') : output.error('no')}`);
195
+ output.writeln(` Ed25519 signature valid: ${sig.signatureValid ? output.success('yes') : output.error('no')}`);
196
+ output.writeln();
197
+ output.writeln(output.bold('Fix verification'));
198
+ for (const r of fileResults) {
199
+ const status = r.status === 'pass'
200
+ ? output.success('pass')
201
+ : r.status === 'drift'
202
+ ? output.warning('drift')
203
+ : output.error(r.status);
204
+ output.writeln(` [${status}] ${r.id} — ${r.desc}`);
205
+ if (r.status === 'drift' && r.localSha256) {
206
+ output.writeln(output.dim(` expected sha256: ${r.sha256.slice(0, 16)}…`));
207
+ output.writeln(output.dim(` local sha256: ${r.localSha256.slice(0, 16)}…`));
208
+ }
209
+ else if (r.status === 'regressed') {
210
+ output.writeln(output.dim(` marker missing: '${r.marker}' not found in ${r.installedPath ?? r.file}`));
211
+ }
212
+ else if (r.status === 'missing') {
213
+ output.writeln(output.dim(` file not found in node_modules: ${r.file}`));
214
+ }
215
+ }
216
+ output.writeln();
217
+ output.writeln(output.bold('Summary'));
218
+ output.writeln(` pass: ${passCount}`);
219
+ output.writeln(` drift: ${driftCount}`);
220
+ output.writeln(` regressed: ${regressedCount}`);
221
+ output.writeln(` missing: ${missingCount}`);
222
+ output.writeln();
223
+ if (allOk) {
224
+ output.printSuccess('All fixes verified. Installed artifact matches the signed witness manifest.');
225
+ return { success: true };
226
+ }
227
+ if (regressedCount > 0) {
228
+ output.printError(`${regressedCount} fix(es) regressed. Markers not found in installed artifact.`);
229
+ }
230
+ if (driftCount > 0) {
231
+ output.printWarning(`${driftCount} fix(es) drifted. Markers present, but file SHA-256 differs (could be a benign edit; inspect the diff).`);
232
+ }
233
+ if (!sig.signatureValid || !sig.manifestHashOk) {
234
+ output.printError('Manifest signature failed verification. The witness file may have been tampered with or corrupted.');
235
+ }
236
+ return { success: false, exitCode: 1 };
237
+ },
238
+ };
239
+ export default verifyCommand;
240
+ //# sourceMappingURL=verify.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.6.19",
3
+ "version": "3.6.21",
4
4
  "type": "module",
5
5
  "description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",