claude-flow 3.22.0 → 3.24.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 (55) hide show
  1. package/.claude/helpers/.helpers-version +1 -0
  2. package/.claude/helpers/auto-memory-hook.mjs +59 -274
  3. package/.claude/helpers/helpers.manifest.json +12 -0
  4. package/.claude/helpers/hook-handler.cjs +132 -112
  5. package/.claude/helpers/intelligence.cjs +992 -196
  6. package/package.json +1 -1
  7. package/v3/@claude-flow/cli/dist/src/commands/memory-backup.d.ts +11 -0
  8. package/v3/@claude-flow/cli/dist/src/commands/memory-backup.js +46 -0
  9. package/v3/@claude-flow/cli/dist/src/commands/memory.js +2 -1
  10. package/v3/@claude-flow/cli/dist/src/config/harness-feedback-applier.d.ts +50 -0
  11. package/v3/@claude-flow/cli/dist/src/config/harness-feedback-applier.js +122 -0
  12. package/v3/@claude-flow/cli/dist/src/config/proven-config-refresh.d.ts +39 -0
  13. package/v3/@claude-flow/cli/dist/src/config/proven-config-refresh.js +154 -0
  14. package/v3/@claude-flow/cli/dist/src/config/proven-config-rvfa.d.ts +23 -0
  15. package/v3/@claude-flow/cli/dist/src/config/proven-config-rvfa.js +73 -0
  16. package/v3/@claude-flow/cli/dist/src/config/proven-config.d.ts +86 -0
  17. package/v3/@claude-flow/cli/dist/src/config/proven-config.js +176 -0
  18. package/v3/@claude-flow/cli/dist/src/index.js +35 -0
  19. package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.d.ts +10 -0
  20. package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.js +107 -18
  21. package/v3/@claude-flow/cli/dist/src/services/daemon-autostart.d.ts +17 -0
  22. package/v3/@claude-flow/cli/dist/src/services/daemon-autostart.js +79 -0
  23. package/v3/@claude-flow/cli/dist/src/services/evolve-proof.d.ts +247 -0
  24. package/v3/@claude-flow/cli/dist/src/services/evolve-proof.js +341 -0
  25. package/v3/@claude-flow/cli/dist/src/services/harness-benchmark.d.ts +68 -0
  26. package/v3/@claude-flow/cli/dist/src/services/harness-benchmark.js +94 -0
  27. package/v3/@claude-flow/cli/dist/src/services/harness-canary.d.ts +60 -0
  28. package/v3/@claude-flow/cli/dist/src/services/harness-canary.js +69 -0
  29. package/v3/@claude-flow/cli/dist/src/services/harness-corpus-harvester.d.ts +51 -0
  30. package/v3/@claude-flow/cli/dist/src/services/harness-corpus-harvester.js +74 -0
  31. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-generations.d.ts +111 -0
  32. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-generations.js +354 -0
  33. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-runtime.d.ts +25 -0
  34. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-runtime.js +101 -0
  35. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel.d.ts +48 -0
  36. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel.js +207 -0
  37. package/v3/@claude-flow/cli/dist/src/services/harness-hosts.d.ts +40 -0
  38. package/v3/@claude-flow/cli/dist/src/services/harness-hosts.js +88 -0
  39. package/v3/@claude-flow/cli/dist/src/services/harness-improvement-ledger.d.ts +63 -0
  40. package/v3/@claude-flow/cli/dist/src/services/harness-improvement-ledger.js +101 -0
  41. package/v3/@claude-flow/cli/dist/src/services/harness-loop.d.ts +53 -0
  42. package/v3/@claude-flow/cli/dist/src/services/harness-loop.js +85 -0
  43. package/v3/@claude-flow/cli/dist/src/services/harness-qualification.d.ts +66 -0
  44. package/v3/@claude-flow/cli/dist/src/services/harness-qualification.js +143 -0
  45. package/v3/@claude-flow/cli/dist/src/services/harness-replay.d.ts +37 -0
  46. package/v3/@claude-flow/cli/dist/src/services/harness-replay.js +92 -0
  47. package/v3/@claude-flow/cli/dist/src/services/harness-verify.d.ts +33 -0
  48. package/v3/@claude-flow/cli/dist/src/services/harness-verify.js +26 -0
  49. package/v3/@claude-flow/cli/dist/src/services/harness-worker.d.ts +23 -0
  50. package/v3/@claude-flow/cli/dist/src/services/harness-worker.js +66 -0
  51. package/v3/@claude-flow/cli/dist/src/services/memory-backup.d.ts +24 -0
  52. package/v3/@claude-flow/cli/dist/src/services/memory-backup.js +94 -0
  53. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +16 -1
  54. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +84 -0
  55. package/v3/@claude-flow/cli/package.json +1 -1
@@ -0,0 +1,66 @@
1
+ export type ProvenanceTier = 'oracle:test-exec' | 'judge:fable' | 'proxy:structural';
2
+ export interface TrajectoryStep {
3
+ action: string;
4
+ tier: ProvenanceTier;
5
+ }
6
+ export interface Trajectory {
7
+ id: string;
8
+ steps: TrajectoryStep[];
9
+ outcome: 'success' | 'failure';
10
+ benchmarkTaskId?: string;
11
+ inputs: unknown;
12
+ recordedOutputs: unknown;
13
+ }
14
+ /** Re-execute a trajectory's recorded inputs and return the outputs. */
15
+ export type ReplayFn = (t: Trajectory) => unknown;
16
+ export interface QualificationResult {
17
+ qualified: boolean;
18
+ reasons: string[];
19
+ }
20
+ /** Fingerprint a trajectory by its shape (step actions + outcome) so identical failures dedupe. */
21
+ export declare function fingerprintTrajectory(t: Trajectory): string;
22
+ /**
23
+ * Invariant Q. `replay` is required — determinism cannot be asserted without
24
+ * re-executing; a trajectory with no verifiable replay is REJECTED (fail-closed).
25
+ */
26
+ export declare function qualifyTrajectory(t: Trajectory, replay?: ReplayFn): QualificationResult;
27
+ export interface AntiPattern {
28
+ fingerprint: string;
29
+ stage: string;
30
+ reasons: string[];
31
+ ts: number;
32
+ }
33
+ /**
34
+ * File-backed avoid-list of rejected trajectories/mutations. JSONL, append-only
35
+ * with a bounded cap: once the file exceeds `maxEntries` it is rewritten to the
36
+ * newest `maxEntries` (rotation), so the archive can never grow unbounded —
37
+ * runaway-storage guard. Deduped by fingerprint at the call site.
38
+ */
39
+ export declare class AntiPatternArchive {
40
+ private readonly filePath;
41
+ private readonly maxEntries;
42
+ constructor(filePath: string, maxEntries?: number);
43
+ private load;
44
+ record(entry: AntiPattern): void;
45
+ has(fingerprint: string): boolean;
46
+ list(): AntiPattern[];
47
+ }
48
+ export interface AdmissionReport {
49
+ qualified: Trajectory[];
50
+ rejected: Array<{
51
+ id: string;
52
+ reasons: string[];
53
+ }>;
54
+ admittedCount: number;
55
+ rejectedCount: number;
56
+ }
57
+ /**
58
+ * Admit a batch of observed trajectories into the candidate dataset. Rejects are
59
+ * recorded to the anti-pattern archive (deduped by fingerprint).
60
+ */
61
+ export declare function admitTrajectories(trajectories: Trajectory[], opts?: {
62
+ replay?: ReplayFn;
63
+ archive?: AntiPatternArchive;
64
+ ts?: number;
65
+ }): AdmissionReport;
66
+ //# sourceMappingURL=harness-qualification.d.ts.map
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Qualification + anti-pattern archive (ADR-176 phase 1).
3
+ *
4
+ * Separates OBSERVATION from TRAINING DATA. Raw observed trajectories are NOT
5
+ * training data — they enter optimization only through the Qualification gate,
6
+ * which enforces:
7
+ *
8
+ * Invariant Q: a trajectory is admitted iff it has
9
+ * (a) complete provenance — every step attributed, tier >= oracle/judge
10
+ * (ADR-171; proxy:structural is triage-only),
11
+ * (b) deterministic replay — re-running its recorded inputs reproduces its
12
+ * recorded outputs, and
13
+ * (c) benchmark attribution — it maps to a task in the versioned corpus.
14
+ *
15
+ * Rejected trajectories are not discarded — they are recorded to the
16
+ * ANTI-PATTERN ARCHIVE (negative learning), so future optimization runs avoid
17
+ * re-discovering identical failures. Zero dependencies, $0.
18
+ */
19
+ import * as fs from 'fs';
20
+ import * as path from 'path';
21
+ import { createHash } from 'crypto';
22
+ /** Deterministic JSON for the replay comparison (recursively key-sorted). */
23
+ function stable(v) {
24
+ const c = (x) => {
25
+ if (Array.isArray(x))
26
+ return x.map(c);
27
+ if (x && typeof x === 'object') {
28
+ const o = {};
29
+ for (const k of Object.keys(x).sort())
30
+ o[k] = c(x[k]);
31
+ return o;
32
+ }
33
+ return x;
34
+ };
35
+ return JSON.stringify(c(v));
36
+ }
37
+ /** Fingerprint a trajectory by its shape (step actions + outcome) so identical failures dedupe. */
38
+ export function fingerprintTrajectory(t) {
39
+ const shape = t.steps.map(s => s.action).join('→') + '#' + t.outcome + '#' + (t.benchmarkTaskId ?? '');
40
+ return createHash('sha256').update(shape).digest('hex').slice(0, 32);
41
+ }
42
+ /**
43
+ * Invariant Q. `replay` is required — determinism cannot be asserted without
44
+ * re-executing; a trajectory with no verifiable replay is REJECTED (fail-closed).
45
+ */
46
+ export function qualifyTrajectory(t, replay) {
47
+ const reasons = [];
48
+ // (a) complete provenance
49
+ if (!t.steps || t.steps.length === 0)
50
+ reasons.push('incomplete: no steps');
51
+ else {
52
+ if (t.steps.some(s => !s.action))
53
+ reasons.push('incomplete provenance: a step has no action');
54
+ if (t.steps.some(s => s.tier === 'proxy:structural'))
55
+ reasons.push('proxy-tier step present (below oracle/judge; ADR-171)');
56
+ }
57
+ // (c) benchmark attribution
58
+ if (!t.benchmarkTaskId)
59
+ reasons.push('no benchmark attribution');
60
+ // (b) deterministic replay
61
+ if (!replay) {
62
+ reasons.push('deterministic replay not verified (no replay fn)');
63
+ }
64
+ else {
65
+ let out;
66
+ try {
67
+ out = replay(t);
68
+ }
69
+ catch (e) {
70
+ reasons.push(`replay threw: ${e?.message ?? e}`);
71
+ out = undefined;
72
+ }
73
+ if (reasons.every(r => !r.startsWith('replay threw')) && stable(out) !== stable(t.recordedOutputs)) {
74
+ reasons.push('replay non-deterministic (outputs differ from recorded)');
75
+ }
76
+ }
77
+ return { qualified: reasons.length === 0, reasons };
78
+ }
79
+ /**
80
+ * File-backed avoid-list of rejected trajectories/mutations. JSONL, append-only
81
+ * with a bounded cap: once the file exceeds `maxEntries` it is rewritten to the
82
+ * newest `maxEntries` (rotation), so the archive can never grow unbounded —
83
+ * runaway-storage guard. Deduped by fingerprint at the call site.
84
+ */
85
+ export class AntiPatternArchive {
86
+ filePath;
87
+ maxEntries;
88
+ constructor(filePath, maxEntries = 10000) {
89
+ this.filePath = filePath;
90
+ this.maxEntries = maxEntries;
91
+ }
92
+ load() {
93
+ try {
94
+ return fs.readFileSync(this.filePath, 'utf-8').split('\n').filter(Boolean).map(l => JSON.parse(l));
95
+ }
96
+ catch {
97
+ return [];
98
+ }
99
+ }
100
+ record(entry) {
101
+ try {
102
+ fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
103
+ const existing = this.load();
104
+ if (existing.length >= this.maxEntries) {
105
+ // Rotate: keep the newest (maxEntries-1) + the new entry.
106
+ const kept = existing.slice(existing.length - (this.maxEntries - 1));
107
+ fs.writeFileSync(this.filePath, kept.concat(entry).map(e => JSON.stringify(e)).join('\n') + '\n', 'utf-8');
108
+ }
109
+ else {
110
+ fs.appendFileSync(this.filePath, JSON.stringify(entry) + '\n', 'utf-8');
111
+ }
112
+ }
113
+ catch { /* best-effort */ }
114
+ }
115
+ has(fingerprint) {
116
+ return this.load().some(a => a.fingerprint === fingerprint);
117
+ }
118
+ list() { return this.load(); }
119
+ }
120
+ /**
121
+ * Admit a batch of observed trajectories into the candidate dataset. Rejects are
122
+ * recorded to the anti-pattern archive (deduped by fingerprint).
123
+ */
124
+ export function admitTrajectories(trajectories, opts = {}) {
125
+ const qualified = [];
126
+ const rejected = [];
127
+ const ts = opts.ts ?? Date.now();
128
+ for (const t of trajectories) {
129
+ const r = qualifyTrajectory(t, opts.replay);
130
+ if (r.qualified) {
131
+ qualified.push(t);
132
+ }
133
+ else {
134
+ rejected.push({ id: t.id, reasons: r.reasons });
135
+ const fp = fingerprintTrajectory(t);
136
+ if (opts.archive && !opts.archive.has(fp)) {
137
+ opts.archive.record({ fingerprint: fp, stage: 'qualification', reasons: r.reasons, ts });
138
+ }
139
+ }
140
+ }
141
+ return { qualified, rejected, admittedCount: qualified.length, rejectedCount: rejected.length };
142
+ }
143
+ //# sourceMappingURL=harness-qualification.js.map
@@ -0,0 +1,37 @@
1
+ /** Content digest of a value (canonical, order-independent). */
2
+ export declare function digest(v: unknown): string;
3
+ export interface RecordedRun {
4
+ id: string;
5
+ inputs: unknown;
6
+ outputDigest: string;
7
+ recordedAt?: number;
8
+ }
9
+ /** Re-executable unit: given inputs, produce outputs. Must be pure for determinism. */
10
+ export type RunFn = (inputs: unknown) => unknown;
11
+ /** Capture a run: execute once and record the output digest. */
12
+ export declare function recordRun(id: string, inputs: unknown, run: RunFn, ts?: number): RecordedRun;
13
+ export interface ReplayResult {
14
+ deterministic: boolean;
15
+ expectedDigest: string;
16
+ actualDigest: string;
17
+ }
18
+ /** Replay a recorded run and check it reproduces the recorded output digest. */
19
+ export declare function verifyReplay(recorded: RecordedRun, run: RunFn): ReplayResult;
20
+ /** Batch predicate for accept(): all recorded runs replay deterministically. */
21
+ export declare function allDeterministic(recorded: RecordedRun[], run: RunFn): boolean;
22
+ /**
23
+ * File-backed store of recorded runs (JSONL). Append-only with a bounded cap:
24
+ * once it exceeds `maxEntries` it rotates to the newest `maxEntries` — so the
25
+ * store can never grow unbounded (runaway-storage guard). `get` returns the
26
+ * latest for an id.
27
+ */
28
+ export declare class ReplayStore {
29
+ private readonly filePath;
30
+ private readonly maxEntries;
31
+ constructor(filePath: string, maxEntries?: number);
32
+ private load;
33
+ record(r: RecordedRun): void;
34
+ get(id: string): RecordedRun | undefined;
35
+ all(): RecordedRun[];
36
+ }
37
+ //# sourceMappingURL=harness-replay.d.ts.map
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Deterministic replay engine (ADR-176 phase 3).
3
+ *
4
+ * The `replay == deterministic` predicate in Invariant Q (qualification) and in
5
+ * accept() requires re-executing a recorded run's inputs and confirming they
6
+ * reproduce the recorded outputs. This module records an output DIGEST at
7
+ * capture time and verifies a replay reproduces it — so a trajectory only
8
+ * counts as training data / promotion evidence if its behavior is reproducible.
9
+ * Zero deps, $0.
10
+ */
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+ import { createHash } from 'crypto';
14
+ function stable(v) {
15
+ const c = (x) => {
16
+ if (Array.isArray(x))
17
+ return x.map(c);
18
+ if (x && typeof x === 'object') {
19
+ const o = {};
20
+ for (const k of Object.keys(x).sort())
21
+ o[k] = c(x[k]);
22
+ return o;
23
+ }
24
+ return x;
25
+ };
26
+ return JSON.stringify(c(v));
27
+ }
28
+ /** Content digest of a value (canonical, order-independent). */
29
+ export function digest(v) {
30
+ return createHash('sha256').update(stable(v)).digest('hex');
31
+ }
32
+ /** Capture a run: execute once and record the output digest. */
33
+ export function recordRun(id, inputs, run, ts) {
34
+ return { id, inputs, outputDigest: digest(run(inputs)), recordedAt: ts };
35
+ }
36
+ /** Replay a recorded run and check it reproduces the recorded output digest. */
37
+ export function verifyReplay(recorded, run) {
38
+ let actual;
39
+ try {
40
+ actual = digest(run(recorded.inputs));
41
+ }
42
+ catch {
43
+ actual = 'THREW';
44
+ }
45
+ return { deterministic: actual === recorded.outputDigest, expectedDigest: recorded.outputDigest, actualDigest: actual };
46
+ }
47
+ /** Batch predicate for accept(): all recorded runs replay deterministically. */
48
+ export function allDeterministic(recorded, run) {
49
+ return recorded.every(r => verifyReplay(r, run).deterministic);
50
+ }
51
+ /**
52
+ * File-backed store of recorded runs (JSONL). Append-only with a bounded cap:
53
+ * once it exceeds `maxEntries` it rotates to the newest `maxEntries` — so the
54
+ * store can never grow unbounded (runaway-storage guard). `get` returns the
55
+ * latest for an id.
56
+ */
57
+ export class ReplayStore {
58
+ filePath;
59
+ maxEntries;
60
+ constructor(filePath, maxEntries = 10000) {
61
+ this.filePath = filePath;
62
+ this.maxEntries = maxEntries;
63
+ }
64
+ load() {
65
+ try {
66
+ return fs.readFileSync(this.filePath, 'utf-8').split('\n').filter(Boolean).map(l => JSON.parse(l));
67
+ }
68
+ catch {
69
+ return [];
70
+ }
71
+ }
72
+ record(r) {
73
+ try {
74
+ fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
75
+ const existing = this.load();
76
+ if (existing.length >= this.maxEntries) {
77
+ const kept = existing.slice(existing.length - (this.maxEntries - 1));
78
+ fs.writeFileSync(this.filePath, kept.concat(r).map(e => JSON.stringify(e)).join('\n') + '\n', 'utf-8');
79
+ }
80
+ else {
81
+ fs.appendFileSync(this.filePath, JSON.stringify(r) + '\n', 'utf-8');
82
+ }
83
+ }
84
+ catch { /* best-effort */ }
85
+ }
86
+ get(id) {
87
+ const all = this.load().filter(r => r.id === id);
88
+ return all.length ? all[all.length - 1] : undefined; // latest wins
89
+ }
90
+ all() { return this.load(); }
91
+ }
92
+ //# sourceMappingURL=harness-replay.js.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Adversarial + drift verify gate (ADR-176 phase 4).
3
+ *
4
+ * Produces the two verdicts accept() consumes beyond held-out benchmarking:
5
+ * - redblue: an adversarial red-team pass (@metaharness/redblue, mock-judge/$0
6
+ * by default, loopback-only, cost-capped),
7
+ * - drift: distance from the current champion (@metaharness/drift_from_history).
8
+ *
9
+ * FAIL-CLOSED: if the adversarial verifier is unavailable (metaharness is an
10
+ * optional dependency — ADR-150), redblue is SKIPPED, and since accept() requires
11
+ * redblue === 'PASS', a candidate CANNOT be promoted without real adversarial
12
+ * evidence. The loop then safely degrades to "observe + benchmark, don't
13
+ * promote" and the current signed champion stands. Runners are injectable so the
14
+ * orchestrator supplies the real metaharness bridge; defaults degrade. $0.
15
+ */
16
+ export type RedblueVerdict = 'PASS' | 'FAIL' | 'SKIPPED';
17
+ export type RedblueRunner = () => Promise<RedblueVerdict>;
18
+ /** Returns the drift distance from the champion (>=0), or a negative value = unavailable. */
19
+ export type DriftRunner = () => Promise<number>;
20
+ export interface VerifyResult {
21
+ redblue: RedblueVerdict;
22
+ drift: number;
23
+ driftThreshold: number;
24
+ driftVerdict: 'ok' | 'regressed' | 'skipped';
25
+ adversarialPass: boolean;
26
+ }
27
+ export interface VerifyOptions {
28
+ redblue?: RedblueRunner;
29
+ drift?: DriftRunner;
30
+ driftThreshold?: number;
31
+ }
32
+ export declare function runVerify(opts?: VerifyOptions): Promise<VerifyResult>;
33
+ //# sourceMappingURL=harness-verify.d.ts.map
@@ -0,0 +1,26 @@
1
+ /** Default redblue: no verifier wired in this module → SKIPPED (fail-closed). */
2
+ const defaultRedblue = async () => 'SKIPPED';
3
+ /** Default drift: no baseline → 0. */
4
+ const defaultDrift = async () => 0;
5
+ export async function runVerify(opts = {}) {
6
+ const driftThreshold = opts.driftThreshold ?? 0.05;
7
+ let redblue;
8
+ try {
9
+ redblue = await (opts.redblue ?? defaultRedblue)();
10
+ }
11
+ catch {
12
+ redblue = 'SKIPPED';
13
+ } // a throwing verifier is not a PASS
14
+ let drift;
15
+ try {
16
+ drift = await (opts.drift ?? defaultDrift)();
17
+ }
18
+ catch {
19
+ drift = -1;
20
+ }
21
+ const driftVerdict = drift < 0 ? 'skipped' : (drift <= driftThreshold ? 'ok' : 'regressed');
22
+ // Fail-closed: promotion requires a real PASS and a non-regressing, non-skipped drift.
23
+ const adversarialPass = redblue === 'PASS' && driftVerdict === 'ok';
24
+ return { redblue, drift, driftThreshold, driftVerdict, adversarialPass };
25
+ }
26
+ //# sourceMappingURL=harness-verify.js.map
@@ -0,0 +1,23 @@
1
+ import { type HarnessLoopOptions } from './harness-loop.js';
2
+ export declare const STAGED_CHAMPION_FILE = "harness-champion.manifest.json";
3
+ export type HarnessWorkerInput = HarnessLoopOptions<any>;
4
+ export type LoadHarnessInput = (projectRoot: string) => HarnessWorkerInput | null;
5
+ export interface HarnessWorkerResult {
6
+ ran: boolean;
7
+ reason: string;
8
+ accepted?: boolean;
9
+ staged?: boolean;
10
+ admitted?: number;
11
+ rejected?: number;
12
+ }
13
+ export declare function harnessLoopOptedIn(): boolean;
14
+ /**
15
+ * Execute one bounded, opt-in harness-loop tick for `projectRoot`. Injectable
16
+ * `loadInput`/`optInOverride` for tests. Best-effort; never throws.
17
+ */
18
+ export declare function runHarnessLoopWorker(projectRoot: string, opts?: {
19
+ loadInput?: LoadHarnessInput;
20
+ maxTrajectories?: number;
21
+ optInOverride?: boolean;
22
+ }): Promise<HarnessWorkerResult>;
23
+ //# sourceMappingURL=harness-worker.d.ts.map
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Harness loop daemon worker (ADR-176 phase 8, part 2).
3
+ *
4
+ * Runs runHarnessLoop() on the daemon's schedule — but strictly bounded, given
5
+ * the runaway-resource posture:
6
+ * - OPT-IN: no-op unless RUFLO_HARNESS_LOOP is truthy. The self-optimizing
7
+ * loop never runs autonomously without an explicit opt-in.
8
+ * - $0: with no optimizer/verifier/canary wired (the default), runHarnessLoop
9
+ * qualifies + benchmarks but promotes nothing (fail-closed). No LLM, no
10
+ * metaharness subprocess, no spend unless the operator opts those in too.
11
+ * - bounded: trajectories capped per tick; the daemon adds single-flight
12
+ * (maxConcurrent) + a 16-min worker timeout + TTL/idle shutdown.
13
+ * - never throws: a worker must not crash the daemon.
14
+ *
15
+ * On acceptance it STAGES the (unsigned) champion manifest for the separate
16
+ * publish/sign step — the worker never signs (no key material in the daemon).
17
+ */
18
+ import * as fs from 'fs';
19
+ import * as path from 'path';
20
+ import { runHarnessLoop } from './harness-loop.js';
21
+ export const STAGED_CHAMPION_FILE = 'harness-champion.manifest.json';
22
+ /** No built-in input source yet — the operator wires a corpus + optimizer. */
23
+ const defaultLoadInput = () => null;
24
+ export function harnessLoopOptedIn() {
25
+ return /^(1|true|yes|on)$/i.test(process.env.RUFLO_HARNESS_LOOP ?? '');
26
+ }
27
+ /**
28
+ * Execute one bounded, opt-in harness-loop tick for `projectRoot`. Injectable
29
+ * `loadInput`/`optInOverride` for tests. Best-effort; never throws.
30
+ */
31
+ export async function runHarnessLoopWorker(projectRoot, opts = {}) {
32
+ try {
33
+ const optedIn = opts.optInOverride ?? harnessLoopOptedIn();
34
+ if (!optedIn)
35
+ return { ran: false, reason: 'opt-in required (RUFLO_HARNESS_LOOP=1)' };
36
+ const input = (opts.loadInput ?? defaultLoadInput)(projectRoot);
37
+ if (!input)
38
+ return { ran: false, reason: 'no harness input (corpus/optimizer) configured' };
39
+ const cap = opts.maxTrajectories ?? 2000;
40
+ const bounded = { ...input, trajectories: input.trajectories.slice(0, cap) };
41
+ const result = await runHarnessLoop(bounded);
42
+ let staged = false;
43
+ if (result.accepted && result.manifest) {
44
+ // Stage the UNSIGNED champion for the separate publish/sign step.
45
+ try {
46
+ const dir = path.join(projectRoot, '.claude-flow');
47
+ fs.mkdirSync(dir, { recursive: true });
48
+ fs.writeFileSync(path.join(dir, STAGED_CHAMPION_FILE), JSON.stringify(result.manifest, null, 2), 'utf-8');
49
+ staged = true;
50
+ }
51
+ catch { /* non-fatal */ }
52
+ }
53
+ return {
54
+ ran: true,
55
+ reason: result.reason,
56
+ accepted: result.accepted,
57
+ staged,
58
+ admitted: result.admitted,
59
+ rejected: result.rejected,
60
+ };
61
+ }
62
+ catch (e) {
63
+ return { ran: false, reason: `error: ${e?.message ?? e}` };
64
+ }
65
+ }
66
+ //# sourceMappingURL=harness-worker.js.map
@@ -0,0 +1,24 @@
1
+ export interface BackupOptions {
2
+ /** Source DB (default: <cwd>/.swarm/memory.db). */
3
+ dbPath?: string;
4
+ /** Destination dir (default: <db dir>/backups). */
5
+ destDir?: string;
6
+ /** Rotation: keep the newest N snapshots (default 7 = a week of nightlies). */
7
+ keep?: number;
8
+ /** Optional offsite: a gs://bucket/prefix to also upload the snapshot to. */
9
+ gcs?: string;
10
+ /** Injected epoch millis (tests pass a fixed value; avoids Date.now in logic). */
11
+ timestamp?: number;
12
+ verbose?: boolean;
13
+ }
14
+ export interface BackupResult {
15
+ backedUp: boolean;
16
+ path?: string;
17
+ sizeBytes?: number;
18
+ rotatedAway?: string[];
19
+ gcsUri?: string;
20
+ skipped?: string;
21
+ }
22
+ export declare function defaultMemoryDbPath(cwd?: string): string;
23
+ export declare function backupMemoryDb(opts?: BackupOptions): Promise<BackupResult>;
24
+ //# sourceMappingURL=memory-backup.d.ts.map
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Vector-memory DB backup.
3
+ *
4
+ * Snapshots `.swarm/memory.db` (the sqlite store holding memory_entries +
5
+ * embeddings + the distilled reasoning_patterns) to a timestamped file using
6
+ * better-sqlite3's ONLINE backup API — a consistent, WAL-safe copy that does not
7
+ * block or corrupt a concurrently-written DB (unlike a naive file copy of a
8
+ * WAL-mode DB). Rotates to keep the last N snapshots and, optionally, uploads
9
+ * offsite to Google Cloud Storage.
10
+ *
11
+ * Used by `memory backup` (manual) and the daemon's nightly `backup` worker.
12
+ * Best-effort + non-destructive: it only reads the source DB and writes new
13
+ * files; it never mutates or deletes the live memory DB.
14
+ */
15
+ import * as fs from 'fs';
16
+ import * as path from 'path';
17
+ export function defaultMemoryDbPath(cwd = process.cwd()) {
18
+ return path.join(cwd, '.swarm', 'memory.db');
19
+ }
20
+ /** ISO timestamp safe for filenames (no ':' or '.'). */
21
+ function fileStamp(ms) {
22
+ return new Date(ms).toISOString().replace(/[:.]/g, '-');
23
+ }
24
+ export async function backupMemoryDb(opts = {}) {
25
+ const dbPath = opts.dbPath ?? defaultMemoryDbPath();
26
+ if (!dbPath || !fs.existsSync(dbPath))
27
+ return { backedUp: false, skipped: 'no-db' };
28
+ let Database;
29
+ try {
30
+ const mod = 'better-sqlite3';
31
+ Database = (await import(mod)).default;
32
+ }
33
+ catch {
34
+ return { backedUp: false, skipped: 'better-sqlite3 unavailable' };
35
+ }
36
+ const destDir = opts.destDir ?? path.join(path.dirname(dbPath), 'backups');
37
+ try {
38
+ fs.mkdirSync(destDir, { recursive: true });
39
+ }
40
+ catch { /* */ }
41
+ const destPath = path.join(destDir, `memory-${fileStamp(opts.timestamp ?? Date.now())}.db`);
42
+ // WAL-safe online backup: read-only source, consistent snapshot to destPath.
43
+ let db;
44
+ try {
45
+ db = new Database(dbPath, { readonly: true });
46
+ await db.backup(destPath);
47
+ db.close();
48
+ }
49
+ catch (e) {
50
+ try {
51
+ db?.close();
52
+ }
53
+ catch { /* */ }
54
+ return { backedUp: false, skipped: `backup failed: ${e?.message ?? e}` };
55
+ }
56
+ let sizeBytes = 0;
57
+ try {
58
+ sizeBytes = fs.statSync(destPath).size;
59
+ }
60
+ catch { /* */ }
61
+ // Rotation — ISO-stamped names sort chronologically, so keep the tail.
62
+ const keep = typeof opts.keep === 'number' && opts.keep > 0 ? opts.keep : 7;
63
+ const rotatedAway = [];
64
+ try {
65
+ const snaps = fs.readdirSync(destDir).filter(f => /^memory-.*\.db$/.test(f)).sort();
66
+ while (snaps.length > keep) {
67
+ const old = snaps.shift();
68
+ try {
69
+ fs.rmSync(path.join(destDir, old), { force: true });
70
+ rotatedAway.push(old);
71
+ }
72
+ catch { /* */ }
73
+ }
74
+ }
75
+ catch { /* */ }
76
+ // Optional offsite to GCS (best-effort; local backup already succeeded).
77
+ let gcsUri;
78
+ if (opts.gcs) {
79
+ try {
80
+ const { execFileSync } = await import('child_process');
81
+ const dest = opts.gcs.replace(/\/+$/, '') + '/' + path.basename(destPath);
82
+ execFileSync('gcloud', ['storage', 'cp', destPath, dest], { stdio: ['ignore', 'ignore', 'inherit'] });
83
+ gcsUri = dest;
84
+ }
85
+ catch { /* offsite failed — local snapshot stands */ }
86
+ }
87
+ if (opts.verbose) {
88
+ console.log(`memory DB backed up → ${destPath} (${Math.round(sizeBytes / 1024)} KB)` +
89
+ (rotatedAway.length ? `, rotated ${rotatedAway.length} old` : '') +
90
+ (gcsUri ? `, offsite ${gcsUri}` : ''));
91
+ }
92
+ return { backedUp: true, path: destPath, sizeBytes, rotatedAway, gcsUri };
93
+ }
94
+ //# sourceMappingURL=memory-backup.js.map
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import { EventEmitter } from 'events';
14
14
  import { HeadlessWorkerExecutor } from './headless-worker-executor.js';
15
- export type WorkerType = 'ultralearn' | 'optimize' | 'consolidate' | 'predict' | 'audit' | 'map' | 'preload' | 'deepdive' | 'document' | 'refactor' | 'benchmark' | 'testgaps';
15
+ export type WorkerType = 'ultralearn' | 'optimize' | 'consolidate' | 'predict' | 'audit' | 'map' | 'preload' | 'deepdive' | 'document' | 'refactor' | 'benchmark' | 'testgaps' | 'backup' | 'harness';
16
16
  interface WorkerConfig {
17
17
  type: WorkerType;
18
18
  intervalMs: number;
@@ -319,6 +319,21 @@ export declare class WorkerDaemon extends EventEmitter {
319
319
  * call defensively — a background worker must never crash the daemon.
320
320
  */
321
321
  private runConsolidateWorker;
322
+ /**
323
+ * Nightly memory-DB backup worker (24h interval). Takes a WAL-safe, consistent
324
+ * snapshot of .swarm/memory.db with rotation (keep last N). Never throws — a
325
+ * worker must not crash the daemon; a skip/error is written to the metrics
326
+ * file. Opt-out by omitting `backup` from `-w`; offsite GCS is opt-in via
327
+ * RUFLO_BACKUP_GCS (a gs:// prefix), retention via RUFLO_BACKUP_KEEP.
328
+ */
329
+ private runBackupWorker;
330
+ /**
331
+ * Self-optimizing harness loop worker (ADR-176 phase 8). Strictly bounded:
332
+ * OPT-IN (RUFLO_HARNESS_LOOP), $0-default (no optimizer/verifier wired => no
333
+ * promotion), trajectory-capped, single-flight via the daemon, never throws.
334
+ * On acceptance the (unsigned) champion is staged for the separate sign step.
335
+ */
336
+ private runHarnessWorker;
322
337
  /**
323
338
  * Local testgaps worker (fallback when headless unavailable)
324
339
  */