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.
- package/.claude/helpers/.helpers-version +1 -0
- package/.claude/helpers/auto-memory-hook.mjs +59 -274
- package/.claude/helpers/helpers.manifest.json +12 -0
- package/.claude/helpers/hook-handler.cjs +132 -112
- package/.claude/helpers/intelligence.cjs +992 -196
- package/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/memory-backup.d.ts +11 -0
- package/v3/@claude-flow/cli/dist/src/commands/memory-backup.js +46 -0
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +2 -1
- package/v3/@claude-flow/cli/dist/src/config/harness-feedback-applier.d.ts +50 -0
- package/v3/@claude-flow/cli/dist/src/config/harness-feedback-applier.js +122 -0
- package/v3/@claude-flow/cli/dist/src/config/proven-config-refresh.d.ts +39 -0
- package/v3/@claude-flow/cli/dist/src/config/proven-config-refresh.js +154 -0
- package/v3/@claude-flow/cli/dist/src/config/proven-config-rvfa.d.ts +23 -0
- package/v3/@claude-flow/cli/dist/src/config/proven-config-rvfa.js +73 -0
- package/v3/@claude-flow/cli/dist/src/config/proven-config.d.ts +86 -0
- package/v3/@claude-flow/cli/dist/src/config/proven-config.js +176 -0
- package/v3/@claude-flow/cli/dist/src/index.js +35 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.d.ts +10 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.js +107 -18
- package/v3/@claude-flow/cli/dist/src/services/daemon-autostart.d.ts +17 -0
- package/v3/@claude-flow/cli/dist/src/services/daemon-autostart.js +79 -0
- package/v3/@claude-flow/cli/dist/src/services/evolve-proof.d.ts +247 -0
- package/v3/@claude-flow/cli/dist/src/services/evolve-proof.js +341 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-benchmark.d.ts +68 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-benchmark.js +94 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-canary.d.ts +60 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-canary.js +69 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-corpus-harvester.d.ts +51 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-corpus-harvester.js +74 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-generations.d.ts +111 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-generations.js +354 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-runtime.d.ts +25 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-runtime.js +101 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-flywheel.d.ts +48 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-flywheel.js +207 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-hosts.d.ts +40 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-hosts.js +88 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-improvement-ledger.d.ts +63 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-improvement-ledger.js +101 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-loop.d.ts +53 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-loop.js +85 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-qualification.d.ts +66 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-qualification.js +143 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-replay.d.ts +37 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-replay.js +92 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-verify.d.ts +33 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-verify.js +26 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-worker.d.ts +23 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-worker.js +66 -0
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.d.ts +24 -0
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.js +94 -0
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +16 -1
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +84 -0
- 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
|
*/
|