@claude-flow/cli 3.38.23 → 3.39.1

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": {
3
- "version": "3.38.23",
3
+ "version": "3.39.1",
4
4
  "files": {
5
5
  "auto-memory-hook.mjs": "85fe05c757421c52137c0bc8545a0896bab6b4714538c2a11d1d0835bfcc8c1c",
6
6
  "hook-handler.cjs": "209d9fafe10e17d1be0866727f6f9cf9ac66f9a0793f1c793a4f58319e8e4583",
@@ -8,6 +8,6 @@
8
8
  "statusline.cjs": "4a48353b4f1566fa4379b00fd0321b6676a22b6cc91fbbd8380ac5183d619468"
9
9
  }
10
10
  },
11
- "signature": "TJPmjOW/sxuw75iuKo1IeJNrgRT42msZz/euuryYE/SLd5XPUmROZLfMgzl0yvDyvHeOYZ3j+P3VI62fCFZ1DQ==",
11
+ "signature": "EhPwGDVoITfqsCvvoqUwiw/7eE1FzCr2b6aQvgGI/U9q7CCiFMR9HRmhZNjT0n722BPSnz8aNHLakAAU0IOoAw==",
12
12
  "algorithm": "ed25519"
13
13
  }
package/README.md CHANGED
@@ -21,6 +21,11 @@
21
21
 
22
22
  **An agent meta-harness for Claude Code and Codex.**
23
23
 
24
+ [![RuFlo Explained — build an AI team that plans, remembers, tests, and improves](docs/assets/ruflo-explained/ch14.jpg)](docs/ruflo-explained.md)
25
+
26
+ **[📖 RuFlo Explained — Build an AI Team That Plans, Remembers, Tests, and Improves](docs/ruflo-explained.md)**
27
+ A 14-chapter guide: from the basic idea to a first useful task, then memory, agent teams, plugins, cost and verification.
28
+
24
29
  </div>
25
30
 
26
31
  > **Agent = Model + Harness.** The model writes; the harness gives it tools, memory, loops, sandboxes, and controls so it can actually work. **Ruflo is the harness** — the execution layer around Claude Code and Codex that adds 100+ specialized agents, coordinated swarms, self-learning memory, federated comms across machines, and enterprise security guardrails. So agents don't just run, they collaborate.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "generation": 6,
4
- "generatedAt": "2026-09-07T17:34:20.768Z",
5
- "gitSha": "6acc6891",
4
+ "generatedAt": "2026-09-09T12:55:40.451Z",
5
+ "gitSha": "d55b1bfe",
6
6
  "catalog": {
7
7
  "agents": 167,
8
8
  "tools": 397,
@@ -40,7 +40,7 @@ declare const STDERR_REDIRECT_PREFIXES: string[];
40
40
  declare const AGENTDB_MOCK_FALLBACK_DROP_PREFIXES: string[];
41
41
  declare const shouldRedirectToStderr: (msg: unknown) => boolean;
42
42
  declare const isAgentdbMockFallbackNoise: (msg: unknown) => boolean;
43
- declare const origWarn: (message?: any, ...optionalParams: any[]) => void;
44
- declare const origLog: (message?: any, ...optionalParams: any[]) => void;
45
- declare const origError: (message?: any, ...optionalParams: any[]) => void;
43
+ declare const origWarn: (...data: any[]) => void;
44
+ declare const origLog: (...data: any[]) => void;
45
+ declare const origError: (...data: any[]) => void;
46
46
  //# sourceMappingURL=log-filters.d.ts.map
@@ -214,7 +214,25 @@ async function describeBackend() {
214
214
  try {
215
215
  const { getHNSWStatus } = await import('../memory/memory-initializer.js');
216
216
  const status = getHNSWStatus();
217
- return status.algorithm === 'hnsw' ? 'sql.js + HNSW' : 'sqlite (bridge, brute-force cosine)';
217
+ if (status.algorithm !== 'hnsw')
218
+ return 'sqlite (bridge, brute-force cosine)';
219
+ // #3228: with the native bridge gated off (the Windows default since
220
+ // 3.38.12), memory tools silently fall back to the sql.js store rather
221
+ // than the AgentDB corpus the bridge would have opened. A bare
222
+ // "sql.js + HNSW" hides that substitution — an upgrade can change which
223
+ // file receives writes while store/retrieve smoke tests still pass,
224
+ // because both operations use the same unintended file. Name the gate.
225
+ try {
226
+ const { shouldDisableNativeBridge, getBridgeFailureReason } = await import('../memory/memory-bridge.js');
227
+ if (shouldDisableNativeBridge()) {
228
+ const reason = getBridgeFailureReason();
229
+ return `sql.js + HNSW (native bridge disabled${reason ? `: ${reason}` : ''})`;
230
+ }
231
+ }
232
+ catch {
233
+ // bridge module unavailable — the plain sql.js label is already accurate
234
+ }
235
+ return 'sql.js + HNSW';
218
236
  }
219
237
  catch {
220
238
  return 'sqlite';
@@ -836,7 +836,17 @@ export function getHNSWStatus() {
836
836
  // name promises — that's false while the bridge is the active path, so
837
837
  // this no longer claims otherwise. Vector search itself still works via
838
838
  // the bridge; `algorithm` tells callers it's brute-force, not HNSW.
839
- if (_bridge && _bridge !== null) {
839
+ //
840
+ // #3228: this branch previously tested only that the bridge MODULE was
841
+ // loaded. On Windows the module imports fine but `getRegistry()` returns
842
+ // null behind the #3024 kill-switch, so every read/write actually goes to
843
+ // the sql.js store while this function still reported the bridge as the
844
+ // active path — `describeBackend()` then printed "sqlite (bridge, ...)"
845
+ // for a store the bridge never touched. Gate on whether the bridge is
846
+ // *permitted to be* the active path, not on whether the module resolved.
847
+ // `shouldDisableNativeBridge()` is sync and a pure function of platform +
848
+ // env, so it is a faithful discriminator here and needs no warm registry.
849
+ if (_bridge && _bridge !== null && !_bridge.shouldDisableNativeBridge()) {
840
850
  return {
841
851
  available: false,
842
852
  initialized: false,
@@ -139,9 +139,44 @@ export declare function collectUnknownReceiptFields(receipt: FlywheelEvaluationR
139
139
  */
140
140
  export declare function canonicalizeJcs(value: unknown): string;
141
141
  export declare function sha256Ref(value: string | Buffer): string;
142
+ /**
143
+ * Enforce ADR-322C rule 2 / conformance-checklist A3 over a receipt payload:
144
+ * "Every fractional value is a canonical decimal string, not a binary float."
145
+ *
146
+ * This lives at the RECEIPT boundary rather than inside `canonicalizeJcs`,
147
+ * which is shared with the proposer envelope (`flywheel-proposer.ts`) and the
148
+ * promotion ledger (`flywheel-transaction.ts`) — structures the contract does
149
+ * not govern. A first attempt put the check in the canonicalizer and broke
150
+ * those callers, which is the reason the scope is spelled out here.
151
+ *
152
+ * Integers and scaled integers stay JSON numbers (currency micros, durations,
153
+ * iteration counts). A fractional JSON number anywhere in the payload is a
154
+ * contract violation, and the error names the path — the original bug
155
+ * (ruvnet/ruflo#3229) needed a live fixture to find precisely because neither
156
+ * verifier said which field was wrong.
157
+ */
158
+ export declare function assertReceiptNumberDomain(value: unknown, path?: string): void;
142
159
  export declare function policyCandidateId(policy: Record<string, unknown>): string;
143
160
  /** UUIDv7 with a 48-bit millisecond timestamp and RFC-4122 variant bits. */
144
161
  export declare function uuidV7(now?: number): string;
162
+ /**
163
+ * Encode an opaque policy object so every fractional value is a scale-12
164
+ * decimal string, per ADR-322C rule 2 and conformance-checklist A3.
165
+ *
166
+ * `candidatePolicy` is declared "Opaque to this contract; its shape is owned by
167
+ * policySchemaVersion. Must still satisfy the ADR-322C number rules" — a rule
168
+ * the JSON Schema cannot express for an opaque object, so nothing enforced it
169
+ * and the producer wrote binary floats (`{"alpha": 0.3, "mmrLambda": 0.5}`).
170
+ * Ruflo's own verifier accepted them because `assertJsonValue` only checked
171
+ * finite-and-not-negative-zero; autogenous's stricter verifier correctly
172
+ * rejected the receipt (ruvnet/ruflo#3229, ruvnet/autogenous#15).
173
+ *
174
+ * Integers stay JSON numbers and only non-integers are encoded, which is what
175
+ * the contract's own example shows: `{"hnswEf": 128, "hybridWeight": "0.65"}`.
176
+ * Recurses through nested objects and arrays, because "every fractional value"
177
+ * is not limited to the top level.
178
+ */
179
+ export declare function encodePolicyFractions(value: unknown): unknown;
145
180
  export declare function computePromotionStatistics(input: {
146
181
  baselineScore: number;
147
182
  candidateScore: number;
@@ -123,8 +123,47 @@ export function canonicalizeJcs(value) {
123
123
  export function sha256Ref(value) {
124
124
  return `sha256:${createHash('sha256').update(value).digest('hex')}`;
125
125
  }
126
+ /**
127
+ * Enforce ADR-322C rule 2 / conformance-checklist A3 over a receipt payload:
128
+ * "Every fractional value is a canonical decimal string, not a binary float."
129
+ *
130
+ * This lives at the RECEIPT boundary rather than inside `canonicalizeJcs`,
131
+ * which is shared with the proposer envelope (`flywheel-proposer.ts`) and the
132
+ * promotion ledger (`flywheel-transaction.ts`) — structures the contract does
133
+ * not govern. A first attempt put the check in the canonicalizer and broke
134
+ * those callers, which is the reason the scope is spelled out here.
135
+ *
136
+ * Integers and scaled integers stay JSON numbers (currency micros, durations,
137
+ * iteration counts). A fractional JSON number anywhere in the payload is a
138
+ * contract violation, and the error names the path — the original bug
139
+ * (ruvnet/ruflo#3229) needed a live fixture to find precisely because neither
140
+ * verifier said which field was wrong.
141
+ */
142
+ export function assertReceiptNumberDomain(value, path = '$') {
143
+ if (typeof value === 'number') {
144
+ if (!Number.isInteger(value)) {
145
+ throw new Error(`fractional number at ${path} must be a scale-12 decimal string (ADR-322C rule 2)`);
146
+ }
147
+ return;
148
+ }
149
+ if (Array.isArray(value)) {
150
+ value.forEach((v, i) => assertReceiptNumberDomain(v, `${path}[${i}]`));
151
+ return;
152
+ }
153
+ if (value && typeof value === 'object') {
154
+ for (const [k, v] of Object.entries(value)) {
155
+ assertReceiptNumberDomain(v, `${path}.${k}`);
156
+ }
157
+ }
158
+ }
126
159
  export function policyCandidateId(policy) {
127
- return sha256Ref(canonicalizeJcs(policy));
160
+ // Encode at the hashing boundary so the content ID is always over the
161
+ // CANONICAL form, whether the caller passed a raw config or an
162
+ // already-encoded policy. `encodePolicyFractions` is idempotent — an
163
+ // encoded value is a string and passes through untouched — so
164
+ // policyCandidateId(raw) === policyCandidateId(encoded), which is what lets
165
+ // `verify` recompute the ID from the payload and still match.
166
+ return sha256Ref(canonicalizeJcs(encodePolicyFractions(policy)));
128
167
  }
129
168
  /** UUIDv7 with a 48-bit millisecond timestamp and RFC-4122 variant bits. */
130
169
  export function uuidV7(now = Date.now()) {
@@ -143,6 +182,38 @@ const decimal = (value, scale = 12) => {
143
182
  const normalized = value.toFixed(scale).replace(/\.?0+$/, '');
144
183
  return normalized === '-0' || normalized === '' ? '0' : normalized;
145
184
  };
185
+ /**
186
+ * Encode an opaque policy object so every fractional value is a scale-12
187
+ * decimal string, per ADR-322C rule 2 and conformance-checklist A3.
188
+ *
189
+ * `candidatePolicy` is declared "Opaque to this contract; its shape is owned by
190
+ * policySchemaVersion. Must still satisfy the ADR-322C number rules" — a rule
191
+ * the JSON Schema cannot express for an opaque object, so nothing enforced it
192
+ * and the producer wrote binary floats (`{"alpha": 0.3, "mmrLambda": 0.5}`).
193
+ * Ruflo's own verifier accepted them because `assertJsonValue` only checked
194
+ * finite-and-not-negative-zero; autogenous's stricter verifier correctly
195
+ * rejected the receipt (ruvnet/ruflo#3229, ruvnet/autogenous#15).
196
+ *
197
+ * Integers stay JSON numbers and only non-integers are encoded, which is what
198
+ * the contract's own example shows: `{"hnswEf": 128, "hybridWeight": "0.65"}`.
199
+ * Recurses through nested objects and arrays, because "every fractional value"
200
+ * is not limited to the top level.
201
+ */
202
+ export function encodePolicyFractions(value) {
203
+ if (typeof value === 'number') {
204
+ if (!Number.isFinite(value))
205
+ throw new Error('policy value must be finite');
206
+ // Integers are exact in JSON and the contract permits scaled integers, so
207
+ // they are left alone; -0 is normalized away by `decimal`.
208
+ return Number.isInteger(value) && !Object.is(value, -0) ? value : decimal(value);
209
+ }
210
+ if (Array.isArray(value))
211
+ return value.map(encodePolicyFractions);
212
+ if (value && typeof value === 'object') {
213
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, encodePolicyFractions(v)]));
214
+ }
215
+ return value;
216
+ }
146
217
  function seedFrom(parts) {
147
218
  const digest = createHash('sha256').update(parts.join('')).digest();
148
219
  return { seed: digest.readUInt32BE(0), hex: digest.toString('hex') };
@@ -239,6 +310,10 @@ function receiptIdentityPayload(payload) {
239
310
  return payload;
240
311
  }
241
312
  function signedBytes(payload) {
313
+ // Assert at the signing boundary: a receipt must never be SIGNED unless it
314
+ // satisfies the number domain it claims. Covers both produce and verify,
315
+ // since both paths sign or re-derive these bytes.
316
+ assertReceiptNumberDomain(payload);
242
317
  return Buffer.concat([
243
318
  Buffer.from(RECEIPT_DOMAIN, 'utf8'),
244
319
  Buffer.from([0]),
@@ -249,7 +324,12 @@ export function createFlywheelReceipt(input) {
249
324
  const now = input.now ?? Date.now();
250
325
  const evaluationRunId = input.evaluationRunId ?? uuidV7(now);
251
326
  const lineageId = input.lineageId ?? uuidV7(now);
252
- const candidateId = policyCandidateId(input.candidatePolicy);
327
+ // Encode ONCE and use the encoded object for both the candidate content ID
328
+ // and the payload, so `verify`'s recomputation of policyCandidateId still
329
+ // matches. Encoding after the ID were computed would make every receipt fail
330
+ // its own 'candidate content ID mismatch' check.
331
+ const candidatePolicy = encodePolicyFractions(input.candidatePolicy);
332
+ const candidateId = policyCandidateId(candidatePolicy);
253
333
  // Verifiers recompute the statistics from the payload's scale-12 decimal
254
334
  // strings — the only values they ever have. The encoded values are therefore
255
335
  // the statistical inputs of record: compute the decision from them, not from
@@ -277,9 +357,23 @@ export function createFlywheelReceipt(input) {
277
357
  evaluationRunId,
278
358
  baselineRef: input.baselineRef,
279
359
  expectedLedgerHead: input.expectedLedgerHead ?? GENESIS_LEDGER_HEAD,
280
- candidatePolicy: input.candidatePolicy,
360
+ candidatePolicy,
281
361
  gateVersion: input.gateVersion ?? statistics.ruleVersion,
282
- policySchemaVersion: input.policySchemaVersion ?? 'ruflo.retrieval-policy/v1',
362
+ // v1 -> v2: the encoding of `candidatePolicy` changed (#3229). Fixing it
363
+ // changes the candidate content ID, so a pre-fix receipt can never match a
364
+ // post-fix champion reference. Bumping the schema version makes the
365
+ // promotion gate refuse those receipts with an ACCURATE reason —
366
+ // 'policy schema changed' (flywheel-transaction.ts:468) — instead of a
367
+ // confusing 'stale baseline' hash mismatch.
368
+ //
369
+ // Signed receipts are deliberately NOT migrated: re-encoding changes the
370
+ // content, which changes the ID, which invalidates the signature. A
371
+ // migration would mean re-signing, i.e. minting new receipts that claim to
372
+ // be old ones, which is what the receipt design exists to prevent. Existing
373
+ // receipts stay valid as history and unpromotable; the champion is
374
+ // re-established through the explicit reset path, which already requires
375
+ // confirmation and a recorded reason.
376
+ policySchemaVersion: input.policySchemaVersion ?? 'ruflo.retrieval-policy/v2',
283
377
  safetyEnvelopeRef: input.safetyEnvelopeRef,
284
378
  ...(input.anchorRef ? { anchorRef: input.anchorRef } : {}),
285
379
  requestedProposer: input.requestedProposer ?? 'local',
@@ -342,6 +436,17 @@ export function createFlywheelReceipt(input) {
342
436
  return receipt;
343
437
  }
344
438
  export function verifyFlywheelReceipt(receipt, trustedPublicKeys) {
439
+ // The enforcement half of #3229. Before this, ruflo verified its own
440
+ // non-conforming receipts because the only number rule was
441
+ // finite-and-not-negative-zero, while autogenous's stricter verifier
442
+ // correctly rejected them. Reported as an error rather than thrown so the
443
+ // caller gets it alongside every other finding.
444
+ try {
445
+ assertReceiptNumberDomain(receipt.payload);
446
+ }
447
+ catch (err) {
448
+ return { valid: false, signed: !!receipt.signature, errors: [err.message] };
449
+ }
345
450
  const errors = [];
346
451
  try {
347
452
  if (receipt.payload.schemaVersion !== RECEIPT_SCHEMA)
@@ -216,7 +216,12 @@ export async function evaluateFlywheelCandidate(projectRoot, deps) {
216
216
  evaluationRunId: deps.evaluationRunId,
217
217
  baselineRef: refOf(baseline),
218
218
  expectedLedgerHead: txState.ledgerHead,
219
- candidatePolicy: candidate,
219
+ // `as unknown as` erased RetrievalConfig's `number` fields and laundered
220
+ // binary floats into a receipt claiming ADR-322C conformance (#3229).
221
+ // createFlywheelReceipt now encodes fractional values to scale-12
222
+ // decimal strings, so a single structural cast is enough and the
223
+ // type-erasing double cast is gone.
224
+ candidatePolicy: { ...candidate },
220
225
  safetyEnvelopeRef,
221
226
  anchorRef: deps.anchorRef,
222
227
  requestedProposer: deps.requestedProposer ?? 'local',
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.38.23",
3
+ "version": "3.39.1",
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",