claude-flow 3.20.0 → 3.21.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.
Files changed (36) hide show
  1. package/package.json +1 -1
  2. package/v3/@claude-flow/cli/dist/src/agenticow/speculative-exploration.d.ts +148 -0
  3. package/v3/@claude-flow/cli/dist/src/agenticow/speculative-exploration.js +218 -0
  4. package/v3/@claude-flow/cli/dist/src/commands/autopilot.js +45 -0
  5. package/v3/@claude-flow/cli/dist/src/commands/doctor.js +56 -0
  6. package/v3/@claude-flow/cli/dist/src/commands/neural.js +309 -1
  7. package/v3/@claude-flow/cli/dist/src/init/executor.js +17 -0
  8. package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +26 -2
  9. package/v3/@claude-flow/cli/dist/src/init/memory-package-resolver.d.ts +53 -0
  10. package/v3/@claude-flow/cli/dist/src/init/memory-package-resolver.js +118 -0
  11. package/v3/@claude-flow/cli/dist/src/mcp-client.js +6 -1
  12. package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.js +33 -0
  13. package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-tools.js +47 -1
  14. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-loader.d.ts +59 -0
  15. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-loader.js +105 -0
  16. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-speculate-tools.d.ts +24 -0
  17. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-speculate-tools.js +202 -0
  18. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-tools.js +9 -82
  19. package/v3/@claude-flow/cli/dist/src/mcp-tools/index.d.ts +1 -0
  20. package/v3/@claude-flow/cli/dist/src/mcp-tools/index.js +2 -0
  21. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +49 -8
  22. package/v3/@claude-flow/cli/dist/src/ruvector/router-trajectory.d.ts +33 -0
  23. package/v3/@claude-flow/cli/dist/src/ruvector/router-trajectory.js +31 -0
  24. package/v3/@claude-flow/cli/dist/src/ruvector/run-transcript-recorder.d.ts +154 -0
  25. package/v3/@claude-flow/cli/dist/src/ruvector/run-transcript-recorder.js +209 -0
  26. package/v3/@claude-flow/cli/dist/src/services/checkpoint-gate.d.ts +140 -0
  27. package/v3/@claude-flow/cli/dist/src/services/checkpoint-gate.js +223 -0
  28. package/v3/@claude-flow/cli/dist/src/services/distill-oracle.d.ts +190 -0
  29. package/v3/@claude-flow/cli/dist/src/services/distill-oracle.js +349 -0
  30. package/v3/@claude-flow/cli/dist/src/services/fable-harness.d.ts +168 -0
  31. package/v3/@claude-flow/cli/dist/src/services/fable-harness.js +347 -0
  32. package/v3/@claude-flow/cli/dist/src/services/swarm-memory-branches.d.ts +135 -0
  33. package/v3/@claude-flow/cli/dist/src/services/swarm-memory-branches.js +213 -0
  34. package/v3/@claude-flow/cli/dist/src/services/weight-eft.d.ts +305 -0
  35. package/v3/@claude-flow/cli/dist/src/services/weight-eft.js +296 -0
  36. package/v3/@claude-flow/cli/package.json +5 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "3.20.0",
3
+ "version": "3.21.1",
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",
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Speculative branch-and-promote — parallel A/B solution exploration over
3
+ * COW memory branches (agenticow step 4).
4
+ *
5
+ * Concept (from agenticow's examples/ab-branches + promotion-pipeline):
6
+ * Fan out N candidate approaches, each on its own Copy-On-Write branch of a
7
+ * shared base `.rvf` memory. Each candidate explores/writes independently
8
+ * against its own branch handle. Score the results, PROMOTE the winner's
9
+ * branch back into base, and DISCARD the losers — which for agenticow means
10
+ * deleting the branch files (162 bytes each), not re-copying GB of state.
11
+ *
12
+ * This is the memory-state analogue of the git-worktree-per-agent pattern
13
+ * used for parallel code agents: cheap speculative forks, keep one, throw the
14
+ * rest away at near-zero cost.
15
+ *
16
+ * This module is intentionally COMPOSED ON TOP of the existing agenticow verbs
17
+ * (`fork` / `promote`) and the shared `_agenticow.ts` helpers — it does not
18
+ * reimplement COW semantics. It is generic over the candidate result type so
19
+ * callers supply their own `fn` (what to do on each branch) and `score`
20
+ * (how good the branch turned out).
21
+ *
22
+ * @module @claude-flow/cli/agenticow/speculative-exploration
23
+ */
24
+ import { type AgenticowApi } from '../mcp-tools/agenticow-loader.js';
25
+ /** A COW memory handle (agenticow `AgenticMemory`), kept `any` to avoid a hard type dep. */
26
+ export type MemoryHandle = any;
27
+ export interface SpeculativeCandidate<TResult> {
28
+ /** Human-readable branch label (validated: `[A-Za-z0-9_.\-:/@]`). */
29
+ label: string;
30
+ /**
31
+ * The exploration to run against this candidate's isolated branch handle.
32
+ * Receives the forked branch (read-through of base ∪ its own edits). May
33
+ * ingest, delete, query, etc. Its return value is fed to `score`.
34
+ */
35
+ fn: (branch: MemoryHandle) => TResult | Promise<TResult>;
36
+ }
37
+ export interface ExploreOptions {
38
+ /**
39
+ * Maps a candidate label to the on-disk path for its branch `.rvf` file.
40
+ * Loser paths are deleted after scoring.
41
+ */
42
+ branchPath: (label: string) => string;
43
+ /** Persist winner-branch + save manifests. Default true. */
44
+ persist?: boolean;
45
+ /**
46
+ * Tie-break: when two candidates tie on score, the earlier one (lower index)
47
+ * wins by default (stable). Set `'last'` to prefer the later candidate.
48
+ */
49
+ tieBreak?: 'first' | 'last';
50
+ /**
51
+ * PROMOTION GATE (ADR-171). A branch is promote-INELIGIBLE unless cleared by a
52
+ * real evaluation oracle, or by an explicitly-accepted Fable judge. When
53
+ * provided, the top-scoring candidate is promoted ONLY if this returns
54
+ * `{cleared:true, by:'oracle:test-exec'}` or `{cleared:true, by:'judge:fable'}`.
55
+ * `proxy:structural` can NEVER clear a promote — score rank alone does not
56
+ * graduate work into base. Omit for legacy score-only promotion (unverified).
57
+ */
58
+ clearance?: (winnerResult: unknown, label: string) => Promise<{
59
+ cleared: boolean;
60
+ by: PromotionProvenance;
61
+ reason?: string;
62
+ }>;
63
+ /**
64
+ * Force the clearance gate even without a `clearance` fn — a missing gate then
65
+ * means the winner is ineligible (fail-closed). Default: gate enforced iff
66
+ * `clearance` is supplied.
67
+ */
68
+ requireClearance?: boolean;
69
+ }
70
+ /** Provenance of a promotion decision (ADR-171 trust tiers). */
71
+ export type PromotionProvenance = 'oracle:test-exec' | 'judge:fable' | 'proxy:structural' | 'unverified';
72
+ /**
73
+ * Causal failure receipt (ADR-171). Emitted for every discarded loser and for
74
+ * an ineligible/failed winner — a rollback that loses *why* is half-useful.
75
+ */
76
+ export interface SpeculativeReceipt {
77
+ label: string;
78
+ score: number;
79
+ /** What the branch changed vs its lineage, when introspectable. */
80
+ diff: {
81
+ added: number[];
82
+ overridden: number[];
83
+ deleted: number[];
84
+ } | null;
85
+ /** Why this branch did not graduate. */
86
+ outcome: 'discarded-loser' | 'winner-ineligible' | 'winner-failed';
87
+ provenance: PromotionProvenance;
88
+ reason?: string;
89
+ }
90
+ export interface SpeculativeBranchOutcome<TResult> {
91
+ label: string;
92
+ path: string;
93
+ score: number;
94
+ result: TResult;
95
+ /** true for the promoted winner, false for discarded losers. */
96
+ kept: boolean;
97
+ }
98
+ export interface SpeculativeResult<TResult> {
99
+ /** Label of the winning (promoted) candidate. */
100
+ winner: string;
101
+ /** label → score for every candidate. */
102
+ scores: Record<string, number>;
103
+ /** Whether the winner was successfully promoted into base. */
104
+ promoted: boolean;
105
+ /** How the promotion decision was reached (ADR-171 provenance). */
106
+ promotedBy: PromotionProvenance;
107
+ /** Human-readable promotion decision, e.g. 'promoted:oracle:test-exec' or 'ineligible:proxy-cannot-clear'. */
108
+ promotionDecision: string;
109
+ /** agenticow promote() stats for the winner ({ ingested, deleted }). */
110
+ promoteStats: {
111
+ ingested: number;
112
+ deleted: number;
113
+ } | null;
114
+ /** Labels of the discarded losers whose branch files were deleted. */
115
+ discarded: string[];
116
+ /** Causal failure receipts for discarded losers + an ineligible winner. */
117
+ receipts: SpeculativeReceipt[];
118
+ /** Per-candidate detail (score, path, result, kept). */
119
+ branches: SpeculativeBranchOutcome<TResult>[];
120
+ }
121
+ /**
122
+ * Fork one branch per candidate off `base`, run each `fn` against its own
123
+ * branch handle, score the results, PROMOTE the best branch back into `base`,
124
+ * and DISCARD (delete the files of) the rest.
125
+ *
126
+ * The caller owns `base`: this function mutates it in-memory via `promote()`
127
+ * but does NOT save it (only the caller knows the base file path). Persist the
128
+ * base yourself after this resolves (e.g. `base.save(manifestFor(basePath))`).
129
+ *
130
+ * @param base An opened agenticow memory handle to branch from.
131
+ * @param candidates The A/B candidates — each `{label, fn}`.
132
+ * @param score Scores a candidate's result; higher wins.
133
+ * @param opts Branch-path mapping + persistence knobs.
134
+ */
135
+ export declare function explore<TResult>(base: MemoryHandle, candidates: SpeculativeCandidate<TResult>[], score: (result: TResult, label: string) => number, opts: ExploreOptions): Promise<SpeculativeResult<TResult>>;
136
+ /**
137
+ * Convenience wrapper that owns the whole lifecycle for a file-path base:
138
+ * loads agenticow (returns `null` when the optional dep is absent), opens the
139
+ * base with its lineage, runs {@link explore}, then persists the mutated base.
140
+ *
141
+ * Returns `null` when agenticow is not installed so callers can emit the
142
+ * standard `{degraded:true}` contract.
143
+ */
144
+ export declare function exploreFromPath<TResult>(basePath: string, candidates: SpeculativeCandidate<TResult>[], score: (result: TResult, label: string) => number, opts: ExploreOptions & {
145
+ dimension?: number;
146
+ api?: AgenticowApi;
147
+ }): Promise<SpeculativeResult<TResult> | null>;
148
+ //# sourceMappingURL=speculative-exploration.d.ts.map
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Speculative branch-and-promote — parallel A/B solution exploration over
3
+ * COW memory branches (agenticow step 4).
4
+ *
5
+ * Concept (from agenticow's examples/ab-branches + promotion-pipeline):
6
+ * Fan out N candidate approaches, each on its own Copy-On-Write branch of a
7
+ * shared base `.rvf` memory. Each candidate explores/writes independently
8
+ * against its own branch handle. Score the results, PROMOTE the winner's
9
+ * branch back into base, and DISCARD the losers — which for agenticow means
10
+ * deleting the branch files (162 bytes each), not re-copying GB of state.
11
+ *
12
+ * This is the memory-state analogue of the git-worktree-per-agent pattern
13
+ * used for parallel code agents: cheap speculative forks, keep one, throw the
14
+ * rest away at near-zero cost.
15
+ *
16
+ * This module is intentionally COMPOSED ON TOP of the existing agenticow verbs
17
+ * (`fork` / `promote`) and the shared `_agenticow.ts` helpers — it does not
18
+ * reimplement COW semantics. It is generic over the candidate result type so
19
+ * callers supply their own `fn` (what to do on each branch) and `score`
20
+ * (how good the branch turned out).
21
+ *
22
+ * @module @claude-flow/cli/agenticow/speculative-exploration
23
+ */
24
+ import { existsSync, rmSync } from 'node:fs';
25
+ import { loadAgenticow, openWithLineage, manifestFor, resolveMemoryPath, validateLabel, } from '../mcp-tools/agenticow-loader.js';
26
+ function safeDiff(branch) {
27
+ try {
28
+ const d = branch.diff?.();
29
+ return d ? { added: d.added ?? [], overridden: d.overridden ?? [], deleted: d.deleted ?? [] } : null;
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ function deleteBranchFiles(path) {
36
+ for (const p of [path, manifestFor(path)]) {
37
+ try {
38
+ if (existsSync(p))
39
+ rmSync(p, { recursive: true, force: true });
40
+ }
41
+ catch {
42
+ /* best-effort discard; a leftover 162-byte file is not fatal */
43
+ }
44
+ }
45
+ }
46
+ /**
47
+ * Fork one branch per candidate off `base`, run each `fn` against its own
48
+ * branch handle, score the results, PROMOTE the best branch back into `base`,
49
+ * and DISCARD (delete the files of) the rest.
50
+ *
51
+ * The caller owns `base`: this function mutates it in-memory via `promote()`
52
+ * but does NOT save it (only the caller knows the base file path). Persist the
53
+ * base yourself after this resolves (e.g. `base.save(manifestFor(basePath))`).
54
+ *
55
+ * @param base An opened agenticow memory handle to branch from.
56
+ * @param candidates The A/B candidates — each `{label, fn}`.
57
+ * @param score Scores a candidate's result; higher wins.
58
+ * @param opts Branch-path mapping + persistence knobs.
59
+ */
60
+ export async function explore(base, candidates, score, opts) {
61
+ if (!base)
62
+ throw new Error('base memory handle is required');
63
+ if (!Array.isArray(candidates) || candidates.length === 0) {
64
+ throw new Error('at least one candidate is required');
65
+ }
66
+ if (typeof score !== 'function')
67
+ throw new Error('score function is required');
68
+ if (!opts || typeof opts.branchPath !== 'function') {
69
+ throw new Error('opts.branchPath(label) is required');
70
+ }
71
+ const persist = opts.persist !== false;
72
+ const tieBreak = opts.tieBreak ?? 'first';
73
+ // Reject duplicate labels up front — two branches to the same file would clash.
74
+ const seen = new Set();
75
+ const live = [];
76
+ // 1) Fork + explore each candidate on its own isolated branch.
77
+ for (const c of candidates) {
78
+ const label = validateLabel(c.label);
79
+ if (seen.has(label))
80
+ throw new Error(`duplicate candidate label: ${label}`);
81
+ seen.add(label);
82
+ if (typeof c.fn !== 'function')
83
+ throw new Error(`candidate ${label} is missing fn()`);
84
+ const path = resolveMemoryPath(opts.branchPath(label));
85
+ const branch = base.fork(label, path);
86
+ const result = await c.fn(branch);
87
+ const s = Number(score(result, label));
88
+ live.push({ label, path, branch, result, score: Number.isFinite(s) ? s : -Infinity });
89
+ }
90
+ // 2) Pick the winner (highest score; stable tie-break).
91
+ let winnerIdx = 0;
92
+ for (let i = 1; i < live.length; i++) {
93
+ const better = tieBreak === 'last'
94
+ ? live[i].score >= live[winnerIdx].score
95
+ : live[i].score > live[winnerIdx].score;
96
+ if (better)
97
+ winnerIdx = i;
98
+ }
99
+ const scores = {};
100
+ for (const l of live)
101
+ scores[l.label] = l.score;
102
+ const winner = live[winnerIdx];
103
+ const receipts = [];
104
+ // 3) PROMOTION GATE (ADR-171). The top score is a *nominee*, not a promotion.
105
+ // A branch graduates into base only when cleared by a real oracle or an
106
+ // explicitly-accepted Fable judge. proxy:structural / score-only never
107
+ // clears. Fail-closed: requireClearance with no gate = ineligible.
108
+ const gate = opts.clearance;
109
+ const enforce = opts.requireClearance ?? !!gate;
110
+ let promoted = false;
111
+ let promotedBy = 'unverified';
112
+ let promotionDecision;
113
+ let promoteStats = null;
114
+ let clearance;
115
+ if (gate) {
116
+ try {
117
+ clearance = await gate(winner.result, winner.label);
118
+ }
119
+ catch (e) {
120
+ clearance = { cleared: false, by: 'proxy:structural', reason: `clearance threw: ${e?.message ?? e}` };
121
+ }
122
+ }
123
+ else if (enforce) {
124
+ clearance = { cleared: false, by: 'proxy:structural', reason: 'requireClearance set but no clearance gate provided' };
125
+ }
126
+ else {
127
+ clearance = { cleared: true, by: 'unverified', reason: 'legacy score-only promotion (no gate configured)' };
128
+ }
129
+ // proxy:structural can NEVER clear a promote, regardless of `cleared`.
130
+ const cleared = clearance.cleared
131
+ && (clearance.by === 'oracle:test-exec' || clearance.by === 'judge:fable' || (!enforce && clearance.by === 'unverified'));
132
+ if (cleared) {
133
+ promoteStats = winner.branch.promote(base);
134
+ if (persist)
135
+ winner.branch.save?.(manifestFor(winner.path));
136
+ winner.branch.close?.();
137
+ promoted = true;
138
+ promotedBy = clearance.by;
139
+ promotionDecision = `promoted:${clearance.by}`;
140
+ }
141
+ else {
142
+ // Winner is ineligible — base stays UNCHANGED. Emit a causal receipt and
143
+ // discard the branch (it did not earn graduation).
144
+ promotedBy = clearance.by;
145
+ promotionDecision = `ineligible:${clearance.by === 'proxy:structural' ? 'proxy-cannot-clear' : clearance.by}`;
146
+ receipts.push({
147
+ label: winner.label,
148
+ score: winner.score,
149
+ diff: safeDiff(winner.branch),
150
+ outcome: 'winner-ineligible',
151
+ provenance: clearance.by,
152
+ reason: clearance.reason ?? 'not cleared by oracle or accepted Fable judge',
153
+ });
154
+ winner.branch.close?.();
155
+ deleteBranchFiles(winner.path);
156
+ }
157
+ // 4) Discard the losers — receipt each, close handle, delete files.
158
+ const discarded = [];
159
+ for (let i = 0; i < live.length; i++) {
160
+ if (i === winnerIdx)
161
+ continue;
162
+ receipts.push({
163
+ label: live[i].label,
164
+ score: live[i].score,
165
+ diff: safeDiff(live[i].branch),
166
+ outcome: 'discarded-loser',
167
+ provenance: 'proxy:structural',
168
+ reason: `lower score than winner (${live[i].score} < ${winner.score})`,
169
+ });
170
+ live[i].branch.close?.();
171
+ deleteBranchFiles(live[i].path);
172
+ discarded.push(live[i].label);
173
+ }
174
+ const branches = live.map((l, i) => ({
175
+ label: l.label,
176
+ path: l.path,
177
+ score: l.score,
178
+ result: l.result,
179
+ kept: i === winnerIdx && promoted,
180
+ }));
181
+ return {
182
+ winner: winner.label,
183
+ scores,
184
+ promoted,
185
+ promotedBy,
186
+ promotionDecision,
187
+ promoteStats,
188
+ discarded,
189
+ receipts,
190
+ branches,
191
+ };
192
+ }
193
+ /**
194
+ * Convenience wrapper that owns the whole lifecycle for a file-path base:
195
+ * loads agenticow (returns `null` when the optional dep is absent), opens the
196
+ * base with its lineage, runs {@link explore}, then persists the mutated base.
197
+ *
198
+ * Returns `null` when agenticow is not installed so callers can emit the
199
+ * standard `{degraded:true}` contract.
200
+ */
201
+ export async function exploreFromPath(basePath, candidates, score, opts) {
202
+ const api = opts.api ?? (await loadAgenticow());
203
+ if (!api)
204
+ return null;
205
+ const resolvedBase = resolveMemoryPath(basePath);
206
+ const base = await openWithLineage(api, resolvedBase, opts.dimension);
207
+ try {
208
+ const result = await explore(base, candidates, score, opts);
209
+ if (opts.persist !== false) {
210
+ base.save?.(manifestFor(resolvedBase));
211
+ }
212
+ return result;
213
+ }
214
+ finally {
215
+ base.close?.();
216
+ }
217
+ }
218
+ //# sourceMappingURL=speculative-exploration.js.map
@@ -6,6 +6,45 @@
6
6
  */
7
7
  import { output } from '../output.js';
8
8
  import { loadState, saveState, appendLog, loadLog, discoverTasks, getProgress, calculateReward, tryLoadLearning, validateNumber, validateTaskSources, LOG_FILE, } from '../autopilot-state.js';
9
+ import { getCheckpointGate, CheckpointGate } from '../services/checkpoint-gate.js';
10
+ /**
11
+ * Opt-in checkpoint/rollback gate for the autopilot tick (agenticow step 3).
12
+ *
13
+ * The autopilot loop's memory mutation happens OUT OF PROCESS — a re-engaged
14
+ * tick returns a continuation prompt, and the spawned agent (plus agentic-flow
15
+ * learning) mutates `.rvf` memory. So there is no in-process fn to wrap with
16
+ * CheckpointGate.guard() here; instead we bracket the loop across ticks:
17
+ * - checkpoint the configured `.rvf` right before handing off a risky tick
18
+ * (re-engage), and
19
+ * - roll it back when the loop's own regression signal fires (stall auto-disable).
20
+ *
21
+ * Entirely opt-in: it does nothing unless CLAUDE_FLOW_AUTOPILOT_CHECKPOINT_MEM
22
+ * points at an `.rvf` file the loop mutates, agenticow is installed, and the
23
+ * CLAUDE_FLOW_AGENTICOW_DISABLE kill switch is unset. Every call is non-fatal —
24
+ * a checkpoint/rollback failure never breaks the loop.
25
+ */
26
+ async function checkpointTick(label) {
27
+ const memPath = CheckpointGate.configuredMemPath();
28
+ if (!memPath)
29
+ return;
30
+ try {
31
+ const r = await getCheckpointGate().checkpoint(memPath, label);
32
+ if (r.ok)
33
+ appendLog({ ts: Date.now(), event: 'checkpoint', label, memPath });
34
+ }
35
+ catch { /* non-fatal: memory branching is best-effort */ }
36
+ }
37
+ async function rollbackTick(reason) {
38
+ const memPath = CheckpointGate.configuredMemPath();
39
+ if (!memPath)
40
+ return;
41
+ try {
42
+ const r = await getCheckpointGate().rollback(memPath);
43
+ if (r.ok)
44
+ appendLog({ ts: Date.now(), event: 'rollback', reason, memPath });
45
+ }
46
+ catch { /* non-fatal */ }
47
+ }
9
48
  // ── Check Handler (for Stop hook) ─────────────────────────────
10
49
  export async function autopilotCheck() {
11
50
  const state = loadState();
@@ -47,6 +86,9 @@ export async function autopilotCheck() {
47
86
  state.enabled = false;
48
87
  saveState(state);
49
88
  appendLog({ ts: Date.now(), event: 'stall-auto-disable', iterations: state.iterations, completed: progress.completed });
89
+ // Regression signal: the loop stalled. Roll `.rvf` memory back to the last
90
+ // good per-tick checkpoint (opt-in via CLAUDE_FLOW_AUTOPILOT_CHECKPOINT_MEM).
91
+ await rollbackTick('stall-auto-disable');
50
92
  return { allowStop: true, reason: `Stalled: no progress in 10 iterations (${progress.completed}/${progress.total} complete)` };
51
93
  }
52
94
  // Re-engage
@@ -54,6 +96,9 @@ export async function autopilotCheck() {
54
96
  state.lastCheck = Date.now();
55
97
  state.history.push({ ts: Date.now(), iteration: state.iterations, completed: progress.completed, total: progress.total });
56
98
  saveState(state);
99
+ // Checkpoint `.rvf` memory before the risky tick runs (opt-in). The next
100
+ // check() rolls back to here if this tick regresses (stall detection above).
101
+ await checkpointTick(`autopilot-iter-${state.iterations}`);
57
102
  const stallWarning = isStalled
58
103
  ? '\nWARNING: No progress in 5 iterations. Consider breaking tasks into smaller subtasks or trying a different approach.'
59
104
  : '';
@@ -13,6 +13,7 @@ import { execSync, exec } from 'child_process';
13
13
  import { promisify } from 'util';
14
14
  import { decodeKey, isEncryptionEnabled } from '../encryption/vault.js';
15
15
  import { isEncryptedBlob } from '../encryption/vault.js';
16
+ import { resolveMemoryPackageFromProject, readMemoryPackageVersion, recordMemoryPackagePath, } from '../init/memory-package-resolver.js';
16
17
  // Promisified exec with proper shell and env inheritance for cross-platform support
17
18
  const execAsync = promisify(exec);
18
19
  /**
@@ -244,6 +245,37 @@ async function checkMemoryDatabase() {
244
245
  }
245
246
  return { name: 'Memory Database', status: 'warn', message: 'Not initialized', fix: 'claude-flow memory configure --backend hybrid' };
246
247
  }
248
+ // #2545: Check that the self-learning bridge can actually load @claude-flow/memory
249
+ // the SAME way the SessionStart auto-memory hook does. On the documented `npx ruflo`
250
+ // path the package lands in the npx cache — unreachable from the project — so the
251
+ // hook silently no-op'd with no signal anywhere. This surfaces it.
252
+ async function checkLearningBridge() {
253
+ const cwd = process.cwd();
254
+ const hookPath = join(cwd, '.claude', 'helpers', 'auto-memory-hook.mjs');
255
+ // Only relevant once init has deployed the hook; otherwise stay quiet.
256
+ if (!existsSync(hookPath)) {
257
+ return {
258
+ name: 'Learning Bridge',
259
+ status: 'pass',
260
+ message: 'auto-memory hook not installed (run: npx ruflo@latest init)',
261
+ };
262
+ }
263
+ const distPath = resolveMemoryPackageFromProject(cwd);
264
+ if (distPath) {
265
+ const version = readMemoryPackageVersion(distPath);
266
+ return {
267
+ name: 'Learning Bridge',
268
+ status: 'pass',
269
+ message: `@claude-flow/memory resolvable${version ? ` (v${version})` : ''}`,
270
+ };
271
+ }
272
+ return {
273
+ name: 'Learning Bridge',
274
+ status: 'fail',
275
+ message: '@claude-flow/memory NOT resolvable — SessionStart self-learning imports are a silent no-op',
276
+ fix: 'npx ruflo@latest doctor --fix (records resolver sidecar) — or: npm i -D @claude-flow/memory',
277
+ };
278
+ }
247
279
  // Check API keys
248
280
  async function checkApiKeys() {
249
281
  const keys = ['ANTHROPIC_API_KEY', 'CLAUDE_API_KEY', 'OPENAI_API_KEY'];
@@ -1036,6 +1068,7 @@ export const doctorCommand = {
1036
1068
  checkStaleSettingsNpx, // #2448 — runaway `npx @latest` in statusLine/hooks
1037
1069
  checkDaemonStatus,
1038
1070
  checkMemoryDatabase,
1071
+ checkLearningBridge, // #2545 — can the auto-memory hook actually load @claude-flow/memory?
1039
1072
  checkApiKeys,
1040
1073
  checkMcpServers,
1041
1074
  checkAIDefence, // #1807
@@ -1057,6 +1090,8 @@ export const doctorCommand = {
1057
1090
  'stale-settings': checkStaleSettingsNpx, // #2448
1058
1091
  'daemon': checkDaemonStatus,
1059
1092
  'memory': checkMemoryDatabase,
1093
+ 'learning': checkLearningBridge, // #2545
1094
+ 'learning-bridge': checkLearningBridge, // #2545
1060
1095
  'api': checkApiKeys,
1061
1096
  'git': checkGit,
1062
1097
  'mcp': checkMcpServers,
@@ -1107,6 +1142,27 @@ export const doctorCommand = {
1107
1142
  spinner.stop();
1108
1143
  output.writeln(output.error('Failed to run health checks'));
1109
1144
  }
1145
+ // #2545: --fix / --install can actually repair the Learning Bridge by
1146
+ // recording the resolver sidecar. When doctor runs via `npx ruflo`, the CLI
1147
+ // CAN resolve its optional @claude-flow/memory dep (it is in the same npx
1148
+ // cache), so writing the sidecar makes the SessionStart hook find it.
1149
+ if ((showFix || autoInstall)) {
1150
+ const lbResult = results.find(r => r.name === 'Learning Bridge');
1151
+ if (lbResult && lbResult.status === 'fail') {
1152
+ const record = recordMemoryPackagePath(process.cwd(), 'doctor');
1153
+ if (record) {
1154
+ const newCheck = await checkLearningBridge();
1155
+ const idx = results.findIndex(r => r.name === 'Learning Bridge');
1156
+ if (idx !== -1)
1157
+ results[idx] = newCheck;
1158
+ const fixIdx = fixes.findIndex(f => f.startsWith('Learning Bridge:'));
1159
+ if (fixIdx !== -1 && newCheck.status === 'pass')
1160
+ fixes.splice(fixIdx, 1);
1161
+ output.writeln(output.success(`Repaired Learning Bridge — wrote .claude-flow/memory-package.json → ${record.distPath}`));
1162
+ output.writeln(formatCheck(newCheck));
1163
+ }
1164
+ }
1165
+ }
1110
1166
  // Auto-install missing dependencies if requested
1111
1167
  if (autoInstall) {
1112
1168
  const claudeCodeResult = results.find(r => r.name === 'Claude Code CLI');