claude-flow 3.23.0 → 3.25.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 (53) hide show
  1. package/.claude/.proven-config-version +1 -0
  2. package/.claude/helpers/.helpers-version +1 -1
  3. package/.claude/helpers/helpers.manifest.json +2 -2
  4. package/.claude/proven-config.json +42 -0
  5. package/package.json +1 -1
  6. package/v3/@claude-flow/cli/dist/src/config/harness-feedback-applier.d.ts +50 -0
  7. package/v3/@claude-flow/cli/dist/src/config/harness-feedback-applier.js +122 -0
  8. package/v3/@claude-flow/cli/dist/src/config/proven-config-refresh.d.ts +39 -0
  9. package/v3/@claude-flow/cli/dist/src/config/proven-config-refresh.js +154 -0
  10. package/v3/@claude-flow/cli/dist/src/config/proven-config-rvfa.d.ts +23 -0
  11. package/v3/@claude-flow/cli/dist/src/config/proven-config-rvfa.js +73 -0
  12. package/v3/@claude-flow/cli/dist/src/config/proven-config.d.ts +86 -0
  13. package/v3/@claude-flow/cli/dist/src/config/proven-config.js +176 -0
  14. package/v3/@claude-flow/cli/dist/src/index.js +35 -0
  15. package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.d.ts +10 -0
  16. package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.js +129 -20
  17. package/v3/@claude-flow/cli/dist/src/ruvector/lattice-wasm.d.ts +14 -0
  18. package/v3/@claude-flow/cli/dist/src/ruvector/lattice-wasm.js +144 -0
  19. package/v3/@claude-flow/cli/dist/src/services/daemon-autostart.d.ts +17 -0
  20. package/v3/@claude-flow/cli/dist/src/services/daemon-autostart.js +79 -0
  21. package/v3/@claude-flow/cli/dist/src/services/evolve-proof.d.ts +253 -0
  22. package/v3/@claude-flow/cli/dist/src/services/evolve-proof.js +343 -0
  23. package/v3/@claude-flow/cli/dist/src/services/harness-benchmark.d.ts +68 -0
  24. package/v3/@claude-flow/cli/dist/src/services/harness-benchmark.js +94 -0
  25. package/v3/@claude-flow/cli/dist/src/services/harness-canary.d.ts +60 -0
  26. package/v3/@claude-flow/cli/dist/src/services/harness-canary.js +69 -0
  27. package/v3/@claude-flow/cli/dist/src/services/harness-corpus-harvester.d.ts +51 -0
  28. package/v3/@claude-flow/cli/dist/src/services/harness-corpus-harvester.js +74 -0
  29. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-generations.d.ts +115 -0
  30. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-generations.js +360 -0
  31. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-runtime.d.ts +25 -0
  32. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-runtime.js +92 -0
  33. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel.d.ts +48 -0
  34. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel.js +207 -0
  35. package/v3/@claude-flow/cli/dist/src/services/harness-frozen-eval.d.ts +22 -0
  36. package/v3/@claude-flow/cli/dist/src/services/harness-frozen-eval.js +66 -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/worker-daemon.d.ts +8 -1
  52. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +42 -0
  53. package/v3/@claude-flow/cli/package.json +1 -1
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Loop orchestrator (ADR-176 phase 8).
3
+ *
4
+ * Composes every gate into one pass:
5
+ * OBSERVE → QUALIFY → BENCHMARK(held-out) → VERIFY(adversarial+drift)
6
+ * → CANARY → ACCEPT(conjunction) → emit champion manifest.
7
+ *
8
+ * $0 + fail-closed by default: with no optimizer/verifier/canary wired, nothing
9
+ * is promoted and the current signed champion stands. A champion manifest is
10
+ * emitted ONLY when every accept() term holds — and it is emitted UNSIGNED here;
11
+ * signing is a separate publish step (scripts/sign-proven-config.mjs, GCP key)
12
+ * so key material never touches the loop. The daemon worker runs this bounded.
13
+ */
14
+ import { admitTrajectories } from './harness-qualification.js';
15
+ import { computeHeldOutSplit, scoreOnTasks, accept, } from './harness-benchmark.js';
16
+ import { runVerify } from './harness-verify.js';
17
+ import { runCanary, compareCanary } from './harness-canary.js';
18
+ export async function runHarnessLoop(opts) {
19
+ const driftThreshold = opts.driftThreshold ?? 0.05;
20
+ // 1. QUALIFY — only receipt-backed, deterministic, benchmark-attributed
21
+ // trajectories become training data; rejects → anti-pattern archive.
22
+ const admission = admitTrajectories(opts.trajectories, { replay: opts.replay, archive: opts.archive, ts: opts.now });
23
+ const base = { admitted: admission.admittedCount, rejected: admission.rejectedCount, accepted: false, reason: '' };
24
+ if (admission.admittedCount === 0)
25
+ return { ...base, reason: 'no qualified trajectories (receipt_coverage=0)' };
26
+ if (opts.candidate === undefined)
27
+ return { ...base, reason: 'no candidate to evaluate' };
28
+ // 2. BENCHMARK — held-out scoring (isolated, reproducible split).
29
+ const { heldOut } = computeHeldOutSplit(opts.corpus.tasks, opts.holdoutFrac ?? 0.2);
30
+ const baselineScore = scoreOnTasks(heldOut, opts.baseline, opts.evalFn, opts.gradeFn).fitness;
31
+ const candidateScore = scoreOnTasks(heldOut, opts.candidate, opts.evalFn, opts.gradeFn).fitness;
32
+ // 3. VERIFY — adversarial + drift (fail-closed: SKIPPED cannot pass).
33
+ const verify = await runVerify({ ...opts.verify, driftThreshold });
34
+ // 4. CANARY — required (separate promotion from deployment); absent => no promote.
35
+ if (!opts.canaryRunner) {
36
+ return { ...base, baselineScore, candidateScore, verify, accepted: false, reason: 'canary required (no runner) — cannot promote' };
37
+ }
38
+ const slice = opts.corpus.tasks.map(t => ({ id: t.id, input: t.input }));
39
+ const canaryCand = runCanary(opts.candidate, slice, opts.canaryRunner);
40
+ const canaryBase = runCanary(opts.baseline, slice, opts.canaryRunner);
41
+ const canaryCmp = compareCanary(canaryCand, canaryBase);
42
+ // 5. ACCEPT — the full conjunction. receipt_coverage=1 by construction (only
43
+ // qualified trajectories are used; rejects were excluded).
44
+ const verdict = {
45
+ heldOutScore: candidateScore,
46
+ baselineHeldOutScore: baselineScore,
47
+ redblue: verify.redblue,
48
+ drift: verify.drift < 0 ? Number.POSITIVE_INFINITY : verify.drift,
49
+ driftThreshold,
50
+ replayDeterministic: !!opts.replay, // qualification already enforced replay determinism on admitted set
51
+ receiptCoverage: 1,
52
+ canaryRollbackRate: canaryCand.rollbackRate,
53
+ baselineRollbackRate: canaryBase.rollbackRate,
54
+ };
55
+ const decision = accept(verdict);
56
+ const result = {
57
+ ...base, baselineScore, candidateScore, verify,
58
+ canary: { candidate: canaryCand, baseline: canaryBase, pass: canaryCmp.pass },
59
+ verdict: decision, accepted: decision.accept,
60
+ reason: decision.accept ? 'accepted — all gates hold' : `rejected — ${decision.failed.join(', ')}`,
61
+ };
62
+ // 6. EMIT the champion manifest (UNSIGNED) on acceptance.
63
+ if (decision.accept && canaryCmp.pass) {
64
+ result.manifest = {
65
+ schema: 'ruflo.proven-config/v1',
66
+ policy: { ref: opts.policyRefOf ? opts.policyRefOf(opts.candidate) : 'sha256:unknown' },
67
+ layer: opts.layer,
68
+ compatibility: { ruflo: '>=3.24.0' },
69
+ benchmark: { corpus: opts.corpus.version, corpusHash: opts.corpus.corpusHash },
70
+ receipt: {
71
+ heldOutDelta: candidateScore - baselineScore,
72
+ redblue: verify.redblue,
73
+ drift: verify.drift,
74
+ canary: { rollbackRate: canaryCand.rollbackRate, latencyP95: canaryCand.latencyP95, costPerTask: canaryCand.costPerTask },
75
+ receiptCoverage: 1,
76
+ },
77
+ };
78
+ }
79
+ else if (decision.accept && !canaryCmp.pass) {
80
+ result.accepted = false;
81
+ result.reason = `rejected — canary regressed (${canaryCmp.failed.join(', ')})`;
82
+ }
83
+ return result;
84
+ }
85
+ //# sourceMappingURL=harness-loop.js.map
@@ -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
@@ -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' | 'backup';
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;
@@ -327,6 +327,13 @@ export declare class WorkerDaemon extends EventEmitter {
327
327
  * RUFLO_BACKUP_GCS (a gs:// prefix), retention via RUFLO_BACKUP_KEEP.
328
328
  */
329
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;
330
337
  /**
331
338
  * Local testgaps worker (fallback when headless unavailable)
332
339
  */
@@ -30,6 +30,7 @@ const DEFAULT_WORKERS = [
30
30
  { type: 'consolidate', intervalMs: 30 * 60 * 1000, offsetMs: 6 * 60 * 1000, priority: 'low', description: 'Memory distillation (ADR-174)', enabled: true },
31
31
  { type: 'testgaps', intervalMs: 20 * 60 * 1000, offsetMs: 8 * 60 * 1000, priority: 'normal', description: 'Test coverage analysis', enabled: true },
32
32
  { type: 'backup', intervalMs: 24 * 60 * 60 * 1000, offsetMs: 10 * 60 * 1000, priority: 'low', description: 'Nightly memory DB backup (WAL-safe, rotated)', enabled: true },
33
+ { type: 'harness', intervalMs: 6 * 60 * 60 * 1000, offsetMs: 12 * 60 * 1000, priority: 'low', description: 'Self-optimizing harness loop (opt-in RUFLO_HARNESS_LOOP, $0-default)', enabled: true },
33
34
  { type: 'predict', intervalMs: 10 * 60 * 1000, offsetMs: 0, priority: 'low', description: 'Predictive preloading', enabled: false },
34
35
  { type: 'document', intervalMs: 60 * 60 * 1000, offsetMs: 0, priority: 'low', description: 'Auto-documentation', enabled: false },
35
36
  ];
@@ -1162,6 +1163,8 @@ export class WorkerDaemon extends EventEmitter {
1162
1163
  return this.runConsolidateWorker();
1163
1164
  case 'backup':
1164
1165
  return this.runBackupWorker();
1166
+ case 'harness':
1167
+ return this.runHarnessWorker();
1165
1168
  case 'testgaps':
1166
1169
  return this.runTestGapsWorkerLocal();
1167
1170
  case 'predict':
@@ -1437,6 +1440,45 @@ export class WorkerDaemon extends EventEmitter {
1437
1440
  writeFileSync(backupFile, JSON.stringify(result, null, 2));
1438
1441
  return result;
1439
1442
  }
1443
+ /**
1444
+ * Self-optimizing harness loop worker (ADR-176 phase 8). Strictly bounded:
1445
+ * OPT-IN (RUFLO_HARNESS_LOOP), $0-default (no optimizer/verifier wired => no
1446
+ * promotion), trajectory-capped, single-flight via the daemon, never throws.
1447
+ * On acceptance the (unsigned) champion is staged for the separate sign step.
1448
+ */
1449
+ async runHarnessWorker() {
1450
+ const metricsDir = join(this.projectRoot, '.claude-flow', 'metrics');
1451
+ if (!existsSync(metricsDir))
1452
+ mkdirSync(metricsDir, { recursive: true });
1453
+ const harnessFile = join(metricsDir, 'harness-loop.json');
1454
+ let result;
1455
+ try {
1456
+ // ADR-176 flywheel (A-P3b autonomy loop): run ONE COMPOUNDING generation
1457
+ // on the install's REAL data — read the persisted champion as baseline,
1458
+ // gate a constrained candidate on the frozen self-supervised held-out with
1459
+ // the human-anchor guard + separate canary, and on a verified promotion
1460
+ // advance the champion so the NEXT tick compounds. Shadow-first (serve lags
1461
+ // one tick). Opt-in ($0 no-op without RUFLO_HARNESS_LOOP).
1462
+ const { runFlywheelGenerationWorker } = await import('./harness-flywheel-runtime.js');
1463
+ const gen = await runFlywheelGenerationWorker(this.projectRoot, { sample: 120 });
1464
+ let status = null;
1465
+ try {
1466
+ const { flywheelStatus } = await import('./harness-flywheel-generations.js');
1467
+ status = flywheelStatus(this.projectRoot);
1468
+ }
1469
+ catch { /* no lineage yet */ }
1470
+ if (gen.promoted)
1471
+ this.log('info', `Flywheel gen ${gen.generation}: PROMOTED (+${(gen.delta ?? 0).toFixed(4)} held-out, significant=${gen.significant}) — champion advanced`);
1472
+ result = { timestamp: new Date().toISOString(), flywheel: gen, lineage: status };
1473
+ }
1474
+ catch (error) {
1475
+ const message = error instanceof Error ? error.message : String(error);
1476
+ this.log('warn', `Harness worker failed: ${message}`);
1477
+ result = { timestamp: new Date().toISOString(), ran: false, error: message };
1478
+ }
1479
+ writeFileSync(harnessFile, JSON.stringify(result, null, 2));
1480
+ return result;
1481
+ }
1440
1482
  /**
1441
1483
  * Local testgaps worker (fallback when headless unavailable)
1442
1484
  */
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.23.0",
3
+ "version": "3.25.0",
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",