claude-flow 3.20.0 → 3.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) 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/neural.js +309 -1
  6. package/v3/@claude-flow/cli/dist/src/mcp-client.js +6 -1
  7. package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.js +33 -0
  8. package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-tools.js +47 -1
  9. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-loader.d.ts +59 -0
  10. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-loader.js +105 -0
  11. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-speculate-tools.d.ts +24 -0
  12. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-speculate-tools.js +202 -0
  13. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-tools.js +9 -82
  14. package/v3/@claude-flow/cli/dist/src/mcp-tools/index.d.ts +1 -0
  15. package/v3/@claude-flow/cli/dist/src/mcp-tools/index.js +2 -0
  16. package/v3/@claude-flow/cli/dist/src/ruvector/router-trajectory.d.ts +33 -0
  17. package/v3/@claude-flow/cli/dist/src/ruvector/router-trajectory.js +31 -0
  18. package/v3/@claude-flow/cli/dist/src/ruvector/run-transcript-recorder.d.ts +154 -0
  19. package/v3/@claude-flow/cli/dist/src/ruvector/run-transcript-recorder.js +209 -0
  20. package/v3/@claude-flow/cli/dist/src/services/checkpoint-gate.d.ts +140 -0
  21. package/v3/@claude-flow/cli/dist/src/services/checkpoint-gate.js +223 -0
  22. package/v3/@claude-flow/cli/dist/src/services/distill-oracle.d.ts +190 -0
  23. package/v3/@claude-flow/cli/dist/src/services/distill-oracle.js +349 -0
  24. package/v3/@claude-flow/cli/dist/src/services/fable-harness.d.ts +168 -0
  25. package/v3/@claude-flow/cli/dist/src/services/fable-harness.js +347 -0
  26. package/v3/@claude-flow/cli/dist/src/services/swarm-memory-branches.d.ts +135 -0
  27. package/v3/@claude-flow/cli/dist/src/services/swarm-memory-branches.js +213 -0
  28. package/v3/@claude-flow/cli/dist/src/services/weight-eft.d.ts +305 -0
  29. package/v3/@claude-flow/cli/dist/src/services/weight-eft.js +296 -0
  30. package/v3/@claude-flow/cli/package.json +5 -4
@@ -0,0 +1,209 @@
1
+ /**
2
+ * run-transcript-recorder.ts — Opt-in FULL run-transcript recorder for the
3
+ * weight-eft training-data export path (agenticow / ADR-150 weight-eft slice).
4
+ *
5
+ * WHY THIS EXISTS
6
+ * ---------------
7
+ * The existing `router-trajectory.ts` recorder captures only the ROUTING
8
+ * DECISION for a task: task text, embedding, scalar quality, tokens, cost.
9
+ * `@metaharness/weight-eft` needs something the routing recorder never had —
10
+ * the full ReAct message TRANSCRIPT, the produced patch, and a resolved
11
+ * boolean — to build SFT/DPO training rows. This module is that missing
12
+ * capture surface. It writes one JSON-line per completed run to
13
+ * `.swarm/run-transcripts.jsonl`, in a shape the archive-builder in
14
+ * `services/weight-eft.ts` maps directly to `DarwinTrajectory[]`.
15
+ *
16
+ * OFF BY DEFAULT (PII / RETENTION SURFACE)
17
+ * ----------------------------------------
18
+ * Rows carry the FULL prompt + assistant transcript + patch — a much larger
19
+ * PII/retention surface than the routing recorder. Mirroring why
20
+ * router-trajectory.ts is off-by-default, every write goes through the
21
+ * `CLAUDE_FLOW_RUN_TRANSCRIPTS=1` env gate. When unset (the default),
22
+ * `recordRunTranscript()` is a no-op. There is no way to enable it implicitly.
23
+ *
24
+ * HONESTY: `resolved` IS A PROXY
25
+ * ------------------------------
26
+ * `DarwinTrajectory.resolved` is meant to be GOLD-resolved status from the
27
+ * official SWE-bench harness. Ruflo has NO SWE-bench oracle. Every record
28
+ * therefore stamps `resolved_source` describing where the boolean actually
29
+ * came from, so no downstream consumer can mistake a proxy for gold:
30
+ * - 'gold-oracle' — a real conformant gold eval supplied it (never
31
+ * ruflo today; reserved for an external caller)
32
+ * - 'output-verifier' — ruflo's structural output-verifier confidence,
33
+ * thresholded — an EXPLICIT proxy
34
+ * - 'api-success' — the model returned without an API error — the
35
+ * weakest proxy (says nothing about correctness)
36
+ * - 'external' — supplied verbatim by the caller, provenance unknown
37
+ *
38
+ * ARCHITECTURAL CONSTRAINTS (mirror router-trajectory.ts / ADR-150)
39
+ * -----------------------------------------------------------------
40
+ * 1. OPT-IN — gated behind CLAUDE_FLOW_RUN_TRANSCRIPTS=1; default off.
41
+ * 2. NEVER THROWS — every fs op is try/caught at the append boundary; a
42
+ * failed write is silent (DEBUG-logged) and never breaks the run.
43
+ * 3. NO metaharness COUPLING — this module imports nothing from
44
+ * `@metaharness/*`; it only defines a portable record shape. The
45
+ * services/weight-eft.ts archive-builder does the (optional) mapping.
46
+ *
47
+ * SCHEMA (versioned, additive) — one JSONL row per completed run:
48
+ * { v, ts, instance_id, task_hash, model, tier, resolved, resolved_source,
49
+ * messages[], model_patch, sample?, source?, tokens?, cost_usd? }
50
+ *
51
+ * @module run-transcript-recorder
52
+ */
53
+ import { appendFileSync, mkdirSync, existsSync, statSync, renameSync, unlinkSync, readFileSync } from 'node:fs';
54
+ import { dirname, join, resolve as resolvePath } from 'node:path';
55
+ import { taskHash } from './router-trajectory.js';
56
+ let _cfg = null;
57
+ let _cachedSize = -1;
58
+ function getConfig() {
59
+ if (_cfg !== null)
60
+ return _cfg;
61
+ const swarmDir = process.env.CLAUDE_FLOW_SWARM_DIR ?? resolvePath(process.cwd(), '.swarm');
62
+ _cfg = {
63
+ enabled: process.env.CLAUDE_FLOW_RUN_TRANSCRIPTS === '1',
64
+ path: process.env.CLAUDE_FLOW_RUN_TRANSCRIPTS_PATH ?? join(swarmDir, 'run-transcripts.jsonl'),
65
+ maxSizeBytes: parseInt(process.env.CLAUDE_FLOW_RUN_TRANSCRIPTS_MAXSIZE ?? `${25 * 1024 * 1024}`, 10) | 0,
66
+ maxRotations: Math.max(0, parseInt(process.env.CLAUDE_FLOW_RUN_TRANSCRIPTS_MAXROTATIONS ?? '3', 10) || 3),
67
+ };
68
+ return _cfg;
69
+ }
70
+ function rotate(cfg) {
71
+ if (!existsSync(cfg.path))
72
+ return;
73
+ try {
74
+ if (cfg.maxRotations === 0) {
75
+ unlinkSync(cfg.path);
76
+ return;
77
+ }
78
+ const oldest = `${cfg.path}.${cfg.maxRotations}`;
79
+ if (existsSync(oldest))
80
+ unlinkSync(oldest);
81
+ for (let i = cfg.maxRotations - 1; i >= 1; i--) {
82
+ const src = `${cfg.path}.${i}`;
83
+ if (existsSync(src))
84
+ renameSync(src, `${cfg.path}.${i + 1}`);
85
+ }
86
+ renameSync(cfg.path, `${cfg.path}.1`);
87
+ }
88
+ catch {
89
+ try {
90
+ unlinkSync(cfg.path);
91
+ }
92
+ catch { /* */ }
93
+ }
94
+ _cachedSize = 0;
95
+ }
96
+ function appendRow(row) {
97
+ const cfg = getConfig();
98
+ if (!cfg.enabled)
99
+ return;
100
+ try {
101
+ const dir = dirname(cfg.path);
102
+ if (!existsSync(dir))
103
+ mkdirSync(dir, { recursive: true });
104
+ if (_cachedSize < 0) {
105
+ _cachedSize = existsSync(cfg.path) ? statSync(cfg.path).size : 0;
106
+ }
107
+ const line = JSON.stringify(row) + '\n';
108
+ const bytes = Buffer.byteLength(line, 'utf8');
109
+ if (cfg.maxSizeBytes > 0 && _cachedSize + bytes > cfg.maxSizeBytes)
110
+ rotate(cfg);
111
+ appendFileSync(cfg.path, line);
112
+ _cachedSize += bytes;
113
+ }
114
+ catch (e) {
115
+ if (process.env.DEBUG) {
116
+ // eslint-disable-next-line no-console
117
+ console.error('run-transcript: appendRow failed:', e.message);
118
+ }
119
+ // Never throw — transcript collection must never break a run.
120
+ }
121
+ }
122
+ // ============================================================================
123
+ // Public API
124
+ // ============================================================================
125
+ /**
126
+ * Record one completed run transcript. Cheap — a single appendFileSync of a
127
+ * JSONL row. No-op when CLAUDE_FLOW_RUN_TRANSCRIPTS is unset (the default).
128
+ * Never throws.
129
+ *
130
+ * `resolvedSource` is REQUIRED so a proxy can never masquerade as gold. If you
131
+ * only have "the API returned", pass 'api-success' — the honest weakest label.
132
+ */
133
+ export function recordRunTranscript(args) {
134
+ const cfg = getConfig();
135
+ const hash = taskHash(args.task);
136
+ const instanceId = args.instanceId ?? `run-${hash}`;
137
+ if (!cfg.enabled)
138
+ return { recorded: false, instanceId, taskHash: hash };
139
+ const row = {
140
+ v: 1,
141
+ ts: new Date().toISOString(),
142
+ instance_id: instanceId,
143
+ task_hash: hash,
144
+ model: args.model,
145
+ tier: args.tier,
146
+ resolved: args.resolved,
147
+ resolved_source: args.resolvedSource,
148
+ messages: args.messages,
149
+ model_patch: args.modelPatch ?? '',
150
+ ...(args.sample !== undefined ? { sample: args.sample } : {}),
151
+ ...(args.source ? { source: args.source } : {}),
152
+ ...(args.tokens ? { tokens: args.tokens } : {}),
153
+ ...(args.costUsd != null ? { cost_usd: args.costUsd } : {}),
154
+ };
155
+ appendRow(row);
156
+ return { recorded: true, instanceId, taskHash: hash };
157
+ }
158
+ /**
159
+ * Read + parse the run-transcript JSONL back into records. Used by the
160
+ * archive-builder (`services/weight-eft.ts`). Malformed lines are skipped and
161
+ * counted, never thrown. Returns `[]` if the file is absent.
162
+ */
163
+ export function readRunTranscripts(path) {
164
+ const p = path ?? getConfig().path;
165
+ if (!existsSync(p))
166
+ return { records: [], malformed: 0, path: p };
167
+ const records = [];
168
+ let malformed = 0;
169
+ try {
170
+ for (const line of readFileSync(p, 'utf8').split('\n')) {
171
+ if (!line.trim())
172
+ continue;
173
+ try {
174
+ const r = JSON.parse(line);
175
+ if (r && typeof r === 'object' && r.v === 1 && r.instance_id && Array.isArray(r.messages)) {
176
+ records.push(r);
177
+ }
178
+ else {
179
+ malformed++;
180
+ }
181
+ }
182
+ catch {
183
+ malformed++;
184
+ }
185
+ }
186
+ }
187
+ catch {
188
+ // unreadable file — treat as empty, never throw
189
+ }
190
+ return { records, malformed, path: p };
191
+ }
192
+ /** Map a ruflo model tier label to the weight-eft policy tier. */
193
+ export function tierForModel(model) {
194
+ // haiku (and any explicitly-cheap label) → 'cheap' (cascade first tier).
195
+ // sonnet / opus / everything else → 'frontier' (the escalation tier).
196
+ if (!model)
197
+ return 'frontier';
198
+ return /haiku/i.test(model) ? 'cheap' : 'frontier';
199
+ }
200
+ /** Diagnostic for status/CLI. */
201
+ export function runTranscriptRecorderStatus() {
202
+ return { ...getConfig() };
203
+ }
204
+ /** @internal — test seam: reset cached config so tests can flip env vars. */
205
+ export function __resetRunTranscriptRecorderForTests() {
206
+ _cfg = null;
207
+ _cachedSize = -1;
208
+ }
209
+ //# sourceMappingURL=run-transcript-recorder.js.map
@@ -0,0 +1,140 @@
1
+ /**
2
+ * CheckpointGate — agenticow-backed checkpoint/rollback gate for autopilot
3
+ * loops and long-horizon workflows (agenticow integration, step 3).
4
+ *
5
+ * ── The pattern ───────────────────────────────────────────────────────────
6
+ * Before a risky loop tick that mutates `.rvf` memory, take an O(1) agenticow
7
+ * checkpoint (162 bytes, fixed cost — see mcp-tools/agenticow-tools.ts). Run
8
+ * the tick. If it regresses — throws, or the caller's verdict says the outcome
9
+ * got worse (verifier fail / error / worse metric) — roll the memory back to
10
+ * the checkpoint. Rollback is O(edits-since-checkpoint), NOT an O(N) rebuild,
11
+ * and the earlier history stays intact via the `.agenticow.json` lineage
12
+ * manifest. On success the checkpoint is simply kept as the new good baseline.
13
+ *
14
+ * The three lifecycle verbs (checkpoint / rollback / promote) already exist in
15
+ * the `agenticow` package and are surfaced as MCP tools in
16
+ * `mcp-tools/agenticow-tools.ts`. This module is the *orchestration hook* that
17
+ * calls them from inside a loop — the MCP surface is for agents, this is for
18
+ * the in-process loop.
19
+ *
20
+ * ── Architectural constraint (ADR-150) ────────────────────────────────────
21
+ * `agenticow` lives in `optionalDependencies` and must NEVER be a hard runtime
22
+ * dependency. This gate degrades gracefully in three ways, each of which runs
23
+ * the tick UNGUARDED rather than failing:
24
+ * 1. package missing → { degraded: true, reason: 'agenticow-not-found' }
25
+ * 2. kill-switch env set → { degraded: true, reason: 'kill-switch' }
26
+ * 3. no memory path configured → { degraded: true, reason: 'no-memory-path' }
27
+ * The package is lazy-loaded on the first guard() call, so importing this
28
+ * module has zero startup cost.
29
+ *
30
+ * The optional-dep loader + path/label/lineage helpers are the canonical ones
31
+ * from `mcp-tools/agenticow-loader.ts` — one implementation shared across every
32
+ * agenticow consumer (verbs, swarm branches, speculative, oracle, this gate).
33
+ *
34
+ * @module @claude-flow/cli/services/checkpoint-gate
35
+ */
36
+ /**
37
+ * Env var pointing at the `.rvf` memory file that a long-horizon loop mutates.
38
+ * When set, the autopilot loop opts in to checkpoint/rollback around each tick.
39
+ * When unset, the gate is a transparent pass-through.
40
+ */
41
+ export declare const CHECKPOINT_MEM_ENV = "CLAUDE_FLOW_AUTOPILOT_CHECKPOINT_MEM";
42
+ /**
43
+ * Kill switch. When truthy (`1`/`true`/`yes`), the gate never touches agenticow
44
+ * and every guard() runs the tick unguarded. Lets an operator disable the
45
+ * feature without changing config or code.
46
+ */
47
+ export declare const KILL_SWITCH_ENV = "CLAUDE_FLOW_AGENTICOW_DISABLE";
48
+ /** Outcome of a guarded tick. `result` is undefined only when the tick threw
49
+ * AND `rethrow` was disabled. */
50
+ export interface CheckpointGuardResult<T> {
51
+ /** The value fn() returned (undefined if it threw with rethrow:false). */
52
+ result: T | undefined;
53
+ /** True when a checkpoint was actually taken before running fn. */
54
+ checkpointed: boolean;
55
+ /** True when memory was rolled back to the checkpoint (throw or regression). */
56
+ rolledBack: boolean;
57
+ /** True when the gate could not engage agenticow and ran fn unguarded. */
58
+ degraded: boolean;
59
+ /** Machine-readable reason for degraded / rollback. */
60
+ reason?: string;
61
+ /** The checkpoint label, when one was taken. */
62
+ checkpointLabel?: string;
63
+ /** The error fn() threw, when rethrow was disabled. */
64
+ error?: unknown;
65
+ }
66
+ export interface GuardOptions<T> {
67
+ /**
68
+ * Verdict function: return true when `result` represents a regression that
69
+ * should trigger a rollback. Defaults to treating `{success:false}` /
70
+ * `{ok:false}` / `{regressed:true}` as regressions.
71
+ */
72
+ isRegression?: (result: T) => boolean;
73
+ /**
74
+ * When the tick throws: roll back, then re-throw the original error
75
+ * (default true). Set false to swallow the error and return it on the
76
+ * result object instead — useful when the loop must never crash.
77
+ */
78
+ rethrow?: boolean;
79
+ /** Optional checkpoint id to roll back to (defaults to most recent). */
80
+ checkpointId?: string;
81
+ }
82
+ export declare class CheckpointGate {
83
+ /** True when the kill-switch env is set to a truthy value. */
84
+ static isKillSwitchSet(): boolean;
85
+ /** The configured loop memory path, or undefined when the feature is off. */
86
+ static configuredMemPath(): string | undefined;
87
+ /**
88
+ * Lazy-load agenticow. Returns null when the package is absent (optional dep)
89
+ * or the kill switch is set. Any *other* import error is re-thrown — a broken
90
+ * install should be loud, a missing optional dep should be silent.
91
+ */
92
+ private load;
93
+ /** True when agenticow can be engaged (present + not killed). */
94
+ available(): Promise<boolean>;
95
+ /**
96
+ * Take a checkpoint on the given `.rvf` memory file. Non-throwing except for
97
+ * validation errors (path traversal, bad label) — those are surfaced so a
98
+ * misconfiguration is caught early.
99
+ */
100
+ checkpoint(memPath: string, label: string): Promise<{
101
+ ok: boolean;
102
+ degraded: boolean;
103
+ reason?: string;
104
+ checkpoint?: unknown;
105
+ }>;
106
+ /**
107
+ * Roll the given `.rvf` memory file back to a checkpoint. Discards edits made
108
+ * since (O(edits-since-checkpoint)). Omit `checkpointId` to target the most
109
+ * recent checkpoint.
110
+ */
111
+ rollback(memPath: string, checkpointId?: string): Promise<{
112
+ ok: boolean;
113
+ degraded: boolean;
114
+ reason?: string;
115
+ result?: unknown;
116
+ }>;
117
+ /**
118
+ * Guard a risky loop tick with a checkpoint/rollback bracket.
119
+ *
120
+ * const outcome = await gate.guard(memPath, 'iter-7', async () => runTick());
121
+ *
122
+ * Behaviour:
123
+ * - No memPath / kill-switch / agenticow-absent → run fn unguarded, degraded:true.
124
+ * - Checkpoint fails to engage (degraded) → run fn unguarded, degraded:true.
125
+ * - fn throws → roll back to checkpoint, then re-throw (unless rethrow:false).
126
+ * - fn returns a regression verdict → roll back, return result with rolledBack:true.
127
+ * - fn succeeds → keep the checkpoint, return result with rolledBack:false.
128
+ *
129
+ * The gate is non-fatal: an internal agenticow failure never masks fn()'s own
130
+ * result — if checkpointing throws, fn still runs unguarded.
131
+ */
132
+ guard<T>(memPath: string | undefined, label: string, fn: () => Promise<T>, opts?: GuardOptions<T>): Promise<CheckpointGuardResult<T>>;
133
+ /** Roll back without throwing — used on the failure path so a rollback error
134
+ * never masks the original tick failure. */
135
+ private safeRollback;
136
+ }
137
+ export declare function getCheckpointGate(): CheckpointGate;
138
+ /** Reset the lazy-load cache — test-only. */
139
+ export declare function __resetCheckpointGateForTests(): void;
140
+ //# sourceMappingURL=checkpoint-gate.d.ts.map
@@ -0,0 +1,223 @@
1
+ /**
2
+ * CheckpointGate — agenticow-backed checkpoint/rollback gate for autopilot
3
+ * loops and long-horizon workflows (agenticow integration, step 3).
4
+ *
5
+ * ── The pattern ───────────────────────────────────────────────────────────
6
+ * Before a risky loop tick that mutates `.rvf` memory, take an O(1) agenticow
7
+ * checkpoint (162 bytes, fixed cost — see mcp-tools/agenticow-tools.ts). Run
8
+ * the tick. If it regresses — throws, or the caller's verdict says the outcome
9
+ * got worse (verifier fail / error / worse metric) — roll the memory back to
10
+ * the checkpoint. Rollback is O(edits-since-checkpoint), NOT an O(N) rebuild,
11
+ * and the earlier history stays intact via the `.agenticow.json` lineage
12
+ * manifest. On success the checkpoint is simply kept as the new good baseline.
13
+ *
14
+ * The three lifecycle verbs (checkpoint / rollback / promote) already exist in
15
+ * the `agenticow` package and are surfaced as MCP tools in
16
+ * `mcp-tools/agenticow-tools.ts`. This module is the *orchestration hook* that
17
+ * calls them from inside a loop — the MCP surface is for agents, this is for
18
+ * the in-process loop.
19
+ *
20
+ * ── Architectural constraint (ADR-150) ────────────────────────────────────
21
+ * `agenticow` lives in `optionalDependencies` and must NEVER be a hard runtime
22
+ * dependency. This gate degrades gracefully in three ways, each of which runs
23
+ * the tick UNGUARDED rather than failing:
24
+ * 1. package missing → { degraded: true, reason: 'agenticow-not-found' }
25
+ * 2. kill-switch env set → { degraded: true, reason: 'kill-switch' }
26
+ * 3. no memory path configured → { degraded: true, reason: 'no-memory-path' }
27
+ * The package is lazy-loaded on the first guard() call, so importing this
28
+ * module has zero startup cost.
29
+ *
30
+ * The optional-dep loader + path/label/lineage helpers are the canonical ones
31
+ * from `mcp-tools/agenticow-loader.ts` — one implementation shared across every
32
+ * agenticow consumer (verbs, swarm branches, speculative, oracle, this gate).
33
+ *
34
+ * @module @claude-flow/cli/services/checkpoint-gate
35
+ */
36
+ import { loadAgenticow, __resetAgenticowCache, resolveMemoryPath, manifestFor, validateLabel, openWithLineage, } from '../mcp-tools/agenticow-loader.js';
37
+ /**
38
+ * Env var pointing at the `.rvf` memory file that a long-horizon loop mutates.
39
+ * When set, the autopilot loop opts in to checkpoint/rollback around each tick.
40
+ * When unset, the gate is a transparent pass-through.
41
+ */
42
+ export const CHECKPOINT_MEM_ENV = 'CLAUDE_FLOW_AUTOPILOT_CHECKPOINT_MEM';
43
+ /**
44
+ * Kill switch. When truthy (`1`/`true`/`yes`), the gate never touches agenticow
45
+ * and every guard() runs the tick unguarded. Lets an operator disable the
46
+ * feature without changing config or code.
47
+ */
48
+ export const KILL_SWITCH_ENV = 'CLAUDE_FLOW_AGENTICOW_DISABLE';
49
+ function defaultIsRegression(result) {
50
+ if (!result || typeof result !== 'object')
51
+ return false;
52
+ const r = result;
53
+ return r.success === false || r.ok === false || r.regressed === true;
54
+ }
55
+ // ── The gate ────────────────────────────────────────────────────────────────
56
+ export class CheckpointGate {
57
+ /** True when the kill-switch env is set to a truthy value. */
58
+ static isKillSwitchSet() {
59
+ const v = (process.env[KILL_SWITCH_ENV] || '').trim().toLowerCase();
60
+ return v === '1' || v === 'true' || v === 'yes' || v === 'on';
61
+ }
62
+ /** The configured loop memory path, or undefined when the feature is off. */
63
+ static configuredMemPath() {
64
+ const p = (process.env[CHECKPOINT_MEM_ENV] || '').trim();
65
+ return p || undefined;
66
+ }
67
+ /**
68
+ * Lazy-load agenticow. Returns null when the package is absent (optional dep)
69
+ * or the kill switch is set. Any *other* import error is re-thrown — a broken
70
+ * install should be loud, a missing optional dep should be silent.
71
+ */
72
+ async load() {
73
+ if (CheckpointGate.isKillSwitchSet())
74
+ return null;
75
+ // Delegates to the canonical loader (#22) — one cache, one degradation
76
+ // policy shared across every agenticow consumer.
77
+ return loadAgenticow();
78
+ }
79
+ /** True when agenticow can be engaged (present + not killed). */
80
+ async available() {
81
+ return (await this.load()) !== null;
82
+ }
83
+ /**
84
+ * Take a checkpoint on the given `.rvf` memory file. Non-throwing except for
85
+ * validation errors (path traversal, bad label) — those are surfaced so a
86
+ * misconfiguration is caught early.
87
+ */
88
+ async checkpoint(memPath, label) {
89
+ const api = await this.load();
90
+ if (!api)
91
+ return { ok: false, degraded: true, reason: CheckpointGate.isKillSwitchSet() ? 'kill-switch' : 'agenticow-not-found' };
92
+ const path = resolveMemoryPath(String(memPath));
93
+ const lbl = validateLabel(String(label));
94
+ const mem = await openWithLineage(api, path);
95
+ try {
96
+ const cp = await mem.checkpoint(lbl);
97
+ await mem.save?.(manifestFor(path));
98
+ return { ok: true, degraded: false, checkpoint: cp };
99
+ }
100
+ finally {
101
+ await mem.close?.();
102
+ }
103
+ }
104
+ /**
105
+ * Roll the given `.rvf` memory file back to a checkpoint. Discards edits made
106
+ * since (O(edits-since-checkpoint)). Omit `checkpointId` to target the most
107
+ * recent checkpoint.
108
+ */
109
+ async rollback(memPath, checkpointId) {
110
+ const api = await this.load();
111
+ if (!api)
112
+ return { ok: false, degraded: true, reason: CheckpointGate.isKillSwitchSet() ? 'kill-switch' : 'agenticow-not-found' };
113
+ const path = resolveMemoryPath(String(memPath));
114
+ const mem = await openWithLineage(api, path);
115
+ try {
116
+ const r = checkpointId ? await mem.rollback(checkpointId) : await mem.rollback();
117
+ await mem.save?.(manifestFor(path));
118
+ return { ok: true, degraded: false, result: r };
119
+ }
120
+ finally {
121
+ await mem.close?.();
122
+ }
123
+ }
124
+ /**
125
+ * Guard a risky loop tick with a checkpoint/rollback bracket.
126
+ *
127
+ * const outcome = await gate.guard(memPath, 'iter-7', async () => runTick());
128
+ *
129
+ * Behaviour:
130
+ * - No memPath / kill-switch / agenticow-absent → run fn unguarded, degraded:true.
131
+ * - Checkpoint fails to engage (degraded) → run fn unguarded, degraded:true.
132
+ * - fn throws → roll back to checkpoint, then re-throw (unless rethrow:false).
133
+ * - fn returns a regression verdict → roll back, return result with rolledBack:true.
134
+ * - fn succeeds → keep the checkpoint, return result with rolledBack:false.
135
+ *
136
+ * The gate is non-fatal: an internal agenticow failure never masks fn()'s own
137
+ * result — if checkpointing throws, fn still runs unguarded.
138
+ */
139
+ async guard(memPath, label, fn, opts = {}) {
140
+ const rethrow = opts.rethrow !== false;
141
+ const isRegression = opts.isRegression ?? defaultIsRegression;
142
+ // Fast paths: feature off → transparent pass-through.
143
+ if (!memPath) {
144
+ const result = await fn();
145
+ return { result, checkpointed: false, rolledBack: false, degraded: true, reason: 'no-memory-path' };
146
+ }
147
+ if (CheckpointGate.isKillSwitchSet()) {
148
+ const result = await fn();
149
+ return { result, checkpointed: false, rolledBack: false, degraded: true, reason: 'kill-switch' };
150
+ }
151
+ // Try to checkpoint. Any failure here degrades to unguarded — the loop must
152
+ // never break because memory branching is unavailable.
153
+ let checkpointed = false;
154
+ try {
155
+ const cp = await this.checkpoint(memPath, label);
156
+ if (!cp.ok) {
157
+ const result = await fn();
158
+ return { result, checkpointed: false, rolledBack: false, degraded: true, reason: cp.reason };
159
+ }
160
+ checkpointed = true;
161
+ }
162
+ catch (err) {
163
+ const result = await fn();
164
+ return {
165
+ result,
166
+ checkpointed: false,
167
+ rolledBack: false,
168
+ degraded: true,
169
+ reason: `checkpoint-error: ${err instanceof Error ? err.message : String(err)}`,
170
+ };
171
+ }
172
+ // Run the tick under the checkpoint.
173
+ let result;
174
+ try {
175
+ result = await fn();
176
+ }
177
+ catch (err) {
178
+ const rb = await this.safeRollback(memPath, opts.checkpointId);
179
+ if (rethrow)
180
+ throw err;
181
+ return {
182
+ result: undefined,
183
+ checkpointed,
184
+ rolledBack: rb,
185
+ degraded: false,
186
+ reason: 'tick-threw',
187
+ checkpointLabel: label,
188
+ error: err,
189
+ };
190
+ }
191
+ // Verdict: regression → roll back, keep the returned result for the caller.
192
+ if (isRegression(result)) {
193
+ const rb = await this.safeRollback(memPath, opts.checkpointId);
194
+ return { result, checkpointed, rolledBack: rb, degraded: false, reason: 'regression-verdict', checkpointLabel: label };
195
+ }
196
+ // Success → keep the checkpoint as the new baseline.
197
+ return { result, checkpointed, rolledBack: false, degraded: false, checkpointLabel: label };
198
+ }
199
+ /** Roll back without throwing — used on the failure path so a rollback error
200
+ * never masks the original tick failure. */
201
+ async safeRollback(memPath, checkpointId) {
202
+ try {
203
+ const r = await this.rollback(memPath, checkpointId);
204
+ return r.ok;
205
+ }
206
+ catch {
207
+ return false;
208
+ }
209
+ }
210
+ }
211
+ /** Shared process-wide gate instance. */
212
+ let _shared = null;
213
+ export function getCheckpointGate() {
214
+ if (!_shared)
215
+ _shared = new CheckpointGate();
216
+ return _shared;
217
+ }
218
+ /** Reset the lazy-load cache — test-only. */
219
+ export function __resetCheckpointGateForTests() {
220
+ __resetAgenticowCache();
221
+ _shared = null;
222
+ }
223
+ //# sourceMappingURL=checkpoint-gate.js.map