claude-flow 3.23.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 -1
- package/.claude/helpers/helpers.manifest.json +2 -2
- package/package.json +1 -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/worker-daemon.d.ts +8 -1
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +42 -0
- package/v3/@claude-flow/cli/package.json +1 -1
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Benchmark corpus + held-out scoring + the accept() promotion gate
|
|
3
|
+
* (ADR-176 phase 2 + the multi-term promotion rule).
|
|
4
|
+
*
|
|
5
|
+
* Proof #1 (measured, held-out): a candidate is scored against a VERSIONED,
|
|
6
|
+
* content-hashed corpus on a held-out split — the "prove-before-ship" pattern
|
|
7
|
+
* distill-tuning.ts established (train/held-out disjoint by index; held-out
|
|
8
|
+
* scored once). Fitness is a composite, not a single gameable number.
|
|
9
|
+
*
|
|
10
|
+
* Promotion is a CONJUNCTION of externally-measurable predicates (ADR-176) —
|
|
11
|
+
* never a scalar. accept() takes the independently-measured verdicts (held-out
|
|
12
|
+
* delta, redblue, drift, deterministic replay, receipt coverage, canary
|
|
13
|
+
* rollback) and admits only when EVERY term holds. Multi-dimensional +
|
|
14
|
+
* Goodhart-resistant. Zero deps, $0.
|
|
15
|
+
*/
|
|
16
|
+
import { createHash } from 'crypto';
|
|
17
|
+
function stable(v) {
|
|
18
|
+
const c = (x) => {
|
|
19
|
+
if (Array.isArray(x))
|
|
20
|
+
return x.map(c);
|
|
21
|
+
if (x && typeof x === 'object') {
|
|
22
|
+
const o = {};
|
|
23
|
+
for (const k of Object.keys(x).sort())
|
|
24
|
+
o[k] = c(x[k]);
|
|
25
|
+
return o;
|
|
26
|
+
}
|
|
27
|
+
return x;
|
|
28
|
+
};
|
|
29
|
+
return JSON.stringify(c(v));
|
|
30
|
+
}
|
|
31
|
+
/** Content hash of a corpus's tasks — the tamper-evident pin for the held-out set. */
|
|
32
|
+
export function hashCorpus(tasks) {
|
|
33
|
+
const canon = tasks.map(t => ({ id: t.id, input: t.input, expected: t.expected, weight: t.weight ?? 1 }))
|
|
34
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
35
|
+
return 'sha256:' + createHash('sha256').update(stable(canon)).digest('hex');
|
|
36
|
+
}
|
|
37
|
+
/** Verify a corpus's declared hash matches its tasks (integrity of the benchmark). */
|
|
38
|
+
export function verifyCorpus(corpus) {
|
|
39
|
+
return hashCorpus(corpus.tasks) === corpus.corpusHash;
|
|
40
|
+
}
|
|
41
|
+
/** Score a candidate over a task set, on isolated tasks (no shared state). */
|
|
42
|
+
export function scoreOnTasks(tasks, candidate, evalFn, gradeFn) {
|
|
43
|
+
if (tasks.length === 0)
|
|
44
|
+
return { fitness: 0, passRate: 0, n: 0 };
|
|
45
|
+
let weighted = 0, totalW = 0, passes = 0;
|
|
46
|
+
for (const t of tasks) {
|
|
47
|
+
const w = t.weight ?? 1;
|
|
48
|
+
let g = 0;
|
|
49
|
+
try {
|
|
50
|
+
g = Math.max(0, Math.min(1, gradeFn(evalFn(t.input, candidate), t.expected)));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
g = 0;
|
|
54
|
+
}
|
|
55
|
+
weighted += g * w;
|
|
56
|
+
totalW += w;
|
|
57
|
+
if (g >= 0.999)
|
|
58
|
+
passes++;
|
|
59
|
+
}
|
|
60
|
+
return { fitness: totalW > 0 ? weighted / totalW : 0, passRate: passes / tasks.length, n: tasks.length };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Deterministic held-out split. Tasks are stably ordered by id, and the last
|
|
64
|
+
* `holdoutFrac` become the held-out set — disjoint from train, reproducible
|
|
65
|
+
* (so two runs converge, per the ADR-176 acceptance test).
|
|
66
|
+
*/
|
|
67
|
+
export function computeHeldOutSplit(tasks, holdoutFrac = 0.2) {
|
|
68
|
+
const ordered = [...tasks].sort((a, b) => a.id.localeCompare(b.id));
|
|
69
|
+
const cut = Math.max(0, ordered.length - Math.max(1, Math.round(ordered.length * holdoutFrac)));
|
|
70
|
+
return { train: ordered.slice(0, cut), heldOut: ordered.slice(cut) };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* accept(candidate) ⟺
|
|
74
|
+
* held_out_score > baseline
|
|
75
|
+
* AND redblue == PASS
|
|
76
|
+
* AND drift <= threshold
|
|
77
|
+
* AND replay == deterministic
|
|
78
|
+
* AND receipt_cov == 100%
|
|
79
|
+
* AND canary.rollback_rate <= baseline
|
|
80
|
+
* Every term is externally measurable; ANY failure → reject.
|
|
81
|
+
*/
|
|
82
|
+
export function accept(v) {
|
|
83
|
+
const terms = {
|
|
84
|
+
held_out_improves: { value: `${v.heldOutScore.toFixed(4)} > ${v.baselineHeldOutScore.toFixed(4)}`, pass: v.heldOutScore > v.baselineHeldOutScore },
|
|
85
|
+
redblue_pass: { value: v.redblue, pass: v.redblue === 'PASS' },
|
|
86
|
+
drift_within: { value: `${v.drift} <= ${v.driftThreshold}`, pass: v.drift <= v.driftThreshold },
|
|
87
|
+
replay_deterministic: { value: v.replayDeterministic, pass: v.replayDeterministic === true },
|
|
88
|
+
receipt_coverage_full: { value: v.receiptCoverage, pass: v.receiptCoverage >= 1 },
|
|
89
|
+
canary_no_worse: { value: `${v.canaryRollbackRate} <= ${v.baselineRollbackRate}`, pass: v.canaryRollbackRate <= v.baselineRollbackRate },
|
|
90
|
+
};
|
|
91
|
+
const failed = Object.entries(terms).filter(([, t]) => !t.pass).map(([k]) => k);
|
|
92
|
+
return { accept: failed.length === 0, terms, failed };
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=harness-benchmark.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canary — separate promotion from deployment (ADR-176 phase 5).
|
|
3
|
+
*
|
|
4
|
+
* Held-out evaluation proves a candidate on FROZEN data; it has not observed
|
|
5
|
+
* real-world behavior. The canary runs the candidate on a bounded, reversible
|
|
6
|
+
* SLICE of live work and measures rollback rate, latency, cost, failure
|
|
7
|
+
* frequency, and acceptance — the telemetry the accept() conjunction needs
|
|
8
|
+
* (canary.rollback_rate <= baseline). Only after canary evidence does PROMOTE
|
|
9
|
+
* fire. This is what stops benchmark-specific evolution from reaching global
|
|
10
|
+
* rollout. Zero deps, $0 (the runner is injected; a real one meters real work).
|
|
11
|
+
*/
|
|
12
|
+
/** One canary run's observed outcome. */
|
|
13
|
+
export interface CanaryOutcome {
|
|
14
|
+
ok: boolean;
|
|
15
|
+
rolledBack: boolean;
|
|
16
|
+
latencyMs: number;
|
|
17
|
+
costUsd: number;
|
|
18
|
+
accepted: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** Executes a candidate on one task input under real (or simulated) conditions. */
|
|
21
|
+
export type CanaryRunner<C> = (input: unknown, candidate: C) => CanaryOutcome;
|
|
22
|
+
export interface CanaryTelemetry {
|
|
23
|
+
n: number;
|
|
24
|
+
rollbackRate: number;
|
|
25
|
+
failureRate: number;
|
|
26
|
+
acceptanceRate: number;
|
|
27
|
+
latencyP95: number;
|
|
28
|
+
latencyMean: number;
|
|
29
|
+
costPerTask: number;
|
|
30
|
+
costTotalUsd: number;
|
|
31
|
+
}
|
|
32
|
+
export interface CanaryOptions {
|
|
33
|
+
sampleFraction?: number;
|
|
34
|
+
maxSamples?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Run the candidate on a bounded, deterministic sample of the slice and
|
|
38
|
+
* aggregate telemetry. Deterministic sampling (stride over id-sorted inputs) so
|
|
39
|
+
* two runs converge (ADR-176 acceptance test).
|
|
40
|
+
*/
|
|
41
|
+
export declare function runCanary<C>(candidate: C, slice: Array<{
|
|
42
|
+
id: string;
|
|
43
|
+
input: unknown;
|
|
44
|
+
}>, runner: CanaryRunner<C>, opts?: CanaryOptions): CanaryTelemetry;
|
|
45
|
+
export interface CanaryComparison {
|
|
46
|
+
pass: boolean;
|
|
47
|
+
checks: Record<string, {
|
|
48
|
+
candidate: number;
|
|
49
|
+
baseline: number;
|
|
50
|
+
pass: boolean;
|
|
51
|
+
}>;
|
|
52
|
+
failed: string[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Canary gate: the candidate must be NO WORSE than the baseline on rollback,
|
|
56
|
+
* latency, and cost (the ADR-176 metrics table constraints). Feeds accept()'s
|
|
57
|
+
* `canary.rollback_rate <= baseline` term plus the latency/cost guards.
|
|
58
|
+
*/
|
|
59
|
+
export declare function compareCanary(candidate: CanaryTelemetry, baseline: CanaryTelemetry, tolerance?: number): CanaryComparison;
|
|
60
|
+
//# sourceMappingURL=harness-canary.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canary — separate promotion from deployment (ADR-176 phase 5).
|
|
3
|
+
*
|
|
4
|
+
* Held-out evaluation proves a candidate on FROZEN data; it has not observed
|
|
5
|
+
* real-world behavior. The canary runs the candidate on a bounded, reversible
|
|
6
|
+
* SLICE of live work and measures rollback rate, latency, cost, failure
|
|
7
|
+
* frequency, and acceptance — the telemetry the accept() conjunction needs
|
|
8
|
+
* (canary.rollback_rate <= baseline). Only after canary evidence does PROMOTE
|
|
9
|
+
* fire. This is what stops benchmark-specific evolution from reaching global
|
|
10
|
+
* rollout. Zero deps, $0 (the runner is injected; a real one meters real work).
|
|
11
|
+
*/
|
|
12
|
+
function percentile(sorted, p) {
|
|
13
|
+
if (sorted.length === 0)
|
|
14
|
+
return 0;
|
|
15
|
+
const idx = Math.min(sorted.length - 1, Math.max(0, Math.ceil(p * sorted.length) - 1));
|
|
16
|
+
return sorted[idx];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Run the candidate on a bounded, deterministic sample of the slice and
|
|
20
|
+
* aggregate telemetry. Deterministic sampling (stride over id-sorted inputs) so
|
|
21
|
+
* two runs converge (ADR-176 acceptance test).
|
|
22
|
+
*/
|
|
23
|
+
export function runCanary(candidate, slice, runner, opts = {}) {
|
|
24
|
+
const frac = opts.sampleFraction ?? 0.1;
|
|
25
|
+
const cap = opts.maxSamples ?? 100;
|
|
26
|
+
const ordered = [...slice].sort((a, b) => a.id.localeCompare(b.id));
|
|
27
|
+
const want = Math.min(cap, Math.max(1, Math.round(ordered.length * frac)));
|
|
28
|
+
// Even stride so the sample spans the slice rather than a contiguous head.
|
|
29
|
+
const stride = Math.max(1, Math.floor(ordered.length / want));
|
|
30
|
+
const sample = [];
|
|
31
|
+
for (let i = 0; i < ordered.length && sample.length < want; i += stride)
|
|
32
|
+
sample.push(ordered[i]);
|
|
33
|
+
const outcomes = sample.map(s => {
|
|
34
|
+
try {
|
|
35
|
+
return runner(s.input, candidate);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return { ok: false, rolledBack: true, latencyMs: 0, costUsd: 0, accepted: false };
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
const n = outcomes.length || 1;
|
|
42
|
+
const lat = outcomes.map(o => o.latencyMs).sort((a, b) => a - b);
|
|
43
|
+
const costTotal = outcomes.reduce((s, o) => s + o.costUsd, 0);
|
|
44
|
+
return {
|
|
45
|
+
n: outcomes.length,
|
|
46
|
+
rollbackRate: outcomes.filter(o => o.rolledBack).length / n,
|
|
47
|
+
failureRate: outcomes.filter(o => !o.ok).length / n,
|
|
48
|
+
acceptanceRate: outcomes.filter(o => o.accepted).length / n,
|
|
49
|
+
latencyP95: percentile(lat, 0.95),
|
|
50
|
+
latencyMean: lat.reduce((s, v) => s + v, 0) / n,
|
|
51
|
+
costPerTask: costTotal / n,
|
|
52
|
+
costTotalUsd: costTotal,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Canary gate: the candidate must be NO WORSE than the baseline on rollback,
|
|
57
|
+
* latency, and cost (the ADR-176 metrics table constraints). Feeds accept()'s
|
|
58
|
+
* `canary.rollback_rate <= baseline` term plus the latency/cost guards.
|
|
59
|
+
*/
|
|
60
|
+
export function compareCanary(candidate, baseline, tolerance = 1e-9) {
|
|
61
|
+
const checks = {
|
|
62
|
+
rollback_no_worse: { candidate: candidate.rollbackRate, baseline: baseline.rollbackRate, pass: candidate.rollbackRate <= baseline.rollbackRate + tolerance },
|
|
63
|
+
latency_no_worse: { candidate: candidate.latencyP95, baseline: baseline.latencyP95, pass: candidate.latencyP95 <= baseline.latencyP95 * (1 + 0.01) },
|
|
64
|
+
cost_no_worse: { candidate: candidate.costPerTask, baseline: baseline.costPerTask, pass: candidate.costPerTask <= baseline.costPerTask * (1 + 0.01) },
|
|
65
|
+
};
|
|
66
|
+
const failed = Object.entries(checks).filter(([, c]) => !c.pass).map(([k]) => k);
|
|
67
|
+
return { pass: failed.length === 0, checks, failed };
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=harness-canary.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface HarvestPattern {
|
|
2
|
+
id: string;
|
|
3
|
+
name?: string;
|
|
4
|
+
content?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface HarvestedTask {
|
|
7
|
+
id: string;
|
|
8
|
+
input: {
|
|
9
|
+
id: string;
|
|
10
|
+
q: string;
|
|
11
|
+
targetId: string;
|
|
12
|
+
};
|
|
13
|
+
expected: string;
|
|
14
|
+
provenanceTier: 'oracle:self-identity';
|
|
15
|
+
}
|
|
16
|
+
export interface BlendedCorpus {
|
|
17
|
+
version: string;
|
|
18
|
+
corpusHash: string;
|
|
19
|
+
anchorIds: string[];
|
|
20
|
+
tasks: Array<{
|
|
21
|
+
id: string;
|
|
22
|
+
input: unknown;
|
|
23
|
+
expected: unknown;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Harvest up to `sample` self-retrieval tasks from real patterns. Deterministic
|
|
28
|
+
* stride sampling (stable across runs); skips docs whose body is too thin to
|
|
29
|
+
* form a discriminative query.
|
|
30
|
+
*/
|
|
31
|
+
export declare function harvestSelfSupervisedTasks(patterns: HarvestPattern[], opts?: {
|
|
32
|
+
sample?: number;
|
|
33
|
+
minQueryTerms?: number;
|
|
34
|
+
}): HarvestedTask[];
|
|
35
|
+
/** Content hash over the task ids+queries — changes iff the corpus changes. */
|
|
36
|
+
export declare function hashBlend(tasks: Array<{
|
|
37
|
+
id: string;
|
|
38
|
+
input: unknown;
|
|
39
|
+
expected: unknown;
|
|
40
|
+
}>): string;
|
|
41
|
+
/**
|
|
42
|
+
* Blend a human-labeled anchor set with harvested tasks into one versioned,
|
|
43
|
+
* hashed corpus. The version encodes the sizes + hash so it visibly grows as the
|
|
44
|
+
* store does; `anchorIds` marks the never-regress subset.
|
|
45
|
+
*/
|
|
46
|
+
export declare function blendCorpus(anchor: Array<{
|
|
47
|
+
id: string;
|
|
48
|
+
input: unknown;
|
|
49
|
+
expected: unknown;
|
|
50
|
+
}>, harvested: HarvestedTask[]): BlendedCorpus;
|
|
51
|
+
//# sourceMappingURL=harness-corpus-harvester.d.ts.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corpus harvester — grows the benchmark yardstick from REAL store data so the
|
|
3
|
+
* flywheel gets a bigger, fresher test set as ruflo is used (ADR-176).
|
|
4
|
+
*
|
|
5
|
+
* Self-supervised self-retrieval: a stored doc is unambiguous ground truth for a
|
|
6
|
+
* query derived from its OWN body. To make it discriminative (so different
|
|
7
|
+
* retrieval configs actually score differently — a usable gradient) the query is
|
|
8
|
+
* built from body keywords with the doc's SUBJECT tokens removed: "can retrieval
|
|
9
|
+
* find this doc from its content when the obvious title words are withheld?"
|
|
10
|
+
* The label is the doc's identity — oracle-grade, not a proxy guess.
|
|
11
|
+
*
|
|
12
|
+
* This grows the auto signal; callers keep the human-labeled seed as a separate
|
|
13
|
+
* NON-REGRESSION anchor (Goodhart guard) so optimizing self-retrieval can never
|
|
14
|
+
* silently drift away from human relevance. Pure + deterministic (no RNG): the
|
|
15
|
+
* sample is a fixed stride, so the same store yields the same corpus.
|
|
16
|
+
*/
|
|
17
|
+
import { createHash } from 'node:crypto';
|
|
18
|
+
const STOP = new Set(['the', 'a', 'an', 'of', 'to', 'in', 'on', 'for', 'and', 'or', 'is', 'was', 'with', 'by', 'at', 'as', 'it', 'this', 'that', 'from', 'be', 'are', 'so', 'if', 'we', 'i', 'you', 'not', 'no', 'do', 'via', 'per']);
|
|
19
|
+
function tokenize(s) {
|
|
20
|
+
return (s.toLowerCase().match(/[a-z0-9][a-z0-9_-]{1,}/g) ?? []).filter((t) => !STOP.has(t));
|
|
21
|
+
}
|
|
22
|
+
/** Top-K body keywords with the subject tokens removed, most-frequent first. */
|
|
23
|
+
function deriveQuery(name, content, k = 6) {
|
|
24
|
+
const nameToks = new Set(tokenize(name));
|
|
25
|
+
const freq = new Map();
|
|
26
|
+
for (const t of tokenize(content)) {
|
|
27
|
+
if (nameToks.has(t))
|
|
28
|
+
continue; // withhold the obvious title words
|
|
29
|
+
freq.set(t, (freq.get(t) ?? 0) + 1);
|
|
30
|
+
}
|
|
31
|
+
return [...freq.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, k).map(([t]) => t).join(' ');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Harvest up to `sample` self-retrieval tasks from real patterns. Deterministic
|
|
35
|
+
* stride sampling (stable across runs); skips docs whose body is too thin to
|
|
36
|
+
* form a discriminative query.
|
|
37
|
+
*/
|
|
38
|
+
export function harvestSelfSupervisedTasks(patterns, opts = {}) {
|
|
39
|
+
const sample = Math.max(1, opts.sample ?? 40);
|
|
40
|
+
const minTerms = opts.minQueryTerms ?? 3;
|
|
41
|
+
// Deterministic order: sort by id, then take an even stride to cover the store.
|
|
42
|
+
const ordered = [...patterns].sort((a, b) => a.id.localeCompare(b.id));
|
|
43
|
+
const stride = Math.max(1, Math.floor(ordered.length / sample));
|
|
44
|
+
const out = [];
|
|
45
|
+
for (let i = 0; i < ordered.length && out.length < sample; i += stride) {
|
|
46
|
+
const p = ordered[i];
|
|
47
|
+
const q = deriveQuery(p.name ?? '', p.content ?? '', 6);
|
|
48
|
+
if (q.split(' ').filter(Boolean).length < minTerms)
|
|
49
|
+
continue; // too thin → skip
|
|
50
|
+
out.push({ id: `hv-${p.id}`, input: { id: `hv-${p.id}`, q, targetId: p.id }, expected: p.id, provenanceTier: 'oracle:self-identity' });
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
/** Content hash over the task ids+queries — changes iff the corpus changes. */
|
|
55
|
+
export function hashBlend(tasks) {
|
|
56
|
+
const canon = tasks.map((t) => `${t.id}|${JSON.stringify(t.input)}|${JSON.stringify(t.expected)}`).sort().join('\n');
|
|
57
|
+
return 'sha256:' + createHash('sha256').update(canon).digest('hex');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Blend a human-labeled anchor set with harvested tasks into one versioned,
|
|
61
|
+
* hashed corpus. The version encodes the sizes + hash so it visibly grows as the
|
|
62
|
+
* store does; `anchorIds` marks the never-regress subset.
|
|
63
|
+
*/
|
|
64
|
+
export function blendCorpus(anchor, harvested) {
|
|
65
|
+
const tasks = [...anchor, ...harvested.map((h) => ({ id: h.id, input: h.input, expected: h.expected }))];
|
|
66
|
+
const corpusHash = hashBlend(tasks);
|
|
67
|
+
return {
|
|
68
|
+
version: `flywheel-a${anchor.length}-h${harvested.length}-${corpusHash.slice(7, 19)}`,
|
|
69
|
+
corpusHash,
|
|
70
|
+
anchorIds: anchor.map((a) => a.id),
|
|
71
|
+
tasks,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=harness-corpus-harvester.js.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { type EvolveReceiptBundle, type LineageTelemetry, type PlateauReport, type MutationStat } from './evolve-proof.js';
|
|
2
|
+
import { type HarvestPattern } from './harness-corpus-harvester.js';
|
|
3
|
+
import { type RetrievalConfig, type RankedItem } from './harness-flywheel.js';
|
|
4
|
+
export declare const FLYWHEEL_DIR: string[];
|
|
5
|
+
export declare const FROZEN_CORPUS = "harvested-selfsup-frozen-v1";
|
|
6
|
+
export interface AnchorTask {
|
|
7
|
+
id: string;
|
|
8
|
+
q: string;
|
|
9
|
+
labels: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface GenerationDeps {
|
|
12
|
+
getPatterns: () => HarvestPattern[] | Promise<HarvestPattern[]>;
|
|
13
|
+
search: (q: string, cfg: RetrievalConfig) => Promise<RankedItem[]> | RankedItem[];
|
|
14
|
+
anchorTasks: AnchorTask[];
|
|
15
|
+
sample?: number;
|
|
16
|
+
now: number;
|
|
17
|
+
applyFn?: (cfg: Record<string, number>, hash: string, generation: number) => void;
|
|
18
|
+
}
|
|
19
|
+
export interface GenerationResult {
|
|
20
|
+
ran: boolean;
|
|
21
|
+
reason: string;
|
|
22
|
+
generation: number;
|
|
23
|
+
promoted?: boolean;
|
|
24
|
+
delta?: number;
|
|
25
|
+
significant?: boolean;
|
|
26
|
+
championConfig?: Record<string, number>;
|
|
27
|
+
servedChampion?: string | null;
|
|
28
|
+
anchorRegressed?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** Promotions only — the champion chain (generation-N.json), sorted by generation. */
|
|
31
|
+
export declare function loadPromotions(root: string): EvolveReceiptBundle[];
|
|
32
|
+
/** Every attempt (promoted + rejected) — for telemetry + mutation-effectiveness. */
|
|
33
|
+
export declare function loadAttempts(root: string): EvolveReceiptBundle[];
|
|
34
|
+
/** The current operating champion (last promotion's config), or defaults. */
|
|
35
|
+
export declare function currentChampion(root: string): {
|
|
36
|
+
config: Record<string, number>;
|
|
37
|
+
hash: string | null;
|
|
38
|
+
generation: number;
|
|
39
|
+
};
|
|
40
|
+
export interface ServedState {
|
|
41
|
+
championHash: string | null;
|
|
42
|
+
config: Record<string, number> | null;
|
|
43
|
+
servedAt: number | null;
|
|
44
|
+
fromGeneration: number | null;
|
|
45
|
+
}
|
|
46
|
+
export declare function servedChampion(root: string): ServedState;
|
|
47
|
+
/**
|
|
48
|
+
* Shadow→serve gate: apply the latest promoted champion to the ACTIVE policy iff
|
|
49
|
+
* it is newer than what is currently served. Called at tick START, so a champion
|
|
50
|
+
* promoted last tick is served this tick (a 1-generation shadow delay) — never
|
|
51
|
+
* auto-served the instant it is promoted.
|
|
52
|
+
*/
|
|
53
|
+
export declare function serveCurrentChampionIfPending(root: string, now: number, applyFn?: GenerationDeps['applyFn']): string | null;
|
|
54
|
+
export interface AxisStat {
|
|
55
|
+
axis: string;
|
|
56
|
+
promotions: number;
|
|
57
|
+
meanDelta: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Which policy AXES have historically paid off — attribute each promotion's
|
|
61
|
+
* held-out Δ to the axes that changed in it. Turns the lineage into a
|
|
62
|
+
* knowledge base the optimizer can act on (not just audit).
|
|
63
|
+
*/
|
|
64
|
+
export declare function axisEffectiveness(promotions: EvolveReceiptBundle[]): AxisStat[];
|
|
65
|
+
/**
|
|
66
|
+
* Candidate set biased by measured axis payoff (meta-learning). Every axis keeps
|
|
67
|
+
* a ±1 exploration floor (never abandon a dimension), but axes with a positive
|
|
68
|
+
* historical Δ get EXPANDED range (±2, ±3) and PAIRWISE joint moves with other
|
|
69
|
+
* productive axes — so compute concentrates on the dimensions that have actually
|
|
70
|
+
* produced gains, instead of a uniform grid. Deterministic; bounded.
|
|
71
|
+
*/
|
|
72
|
+
export declare function biasedGrid(c: RetrievalConfig, ranking: AxisStat[]): RetrievalConfig[];
|
|
73
|
+
/**
|
|
74
|
+
* Run ONE generation against `root`, compounding on the persisted champion.
|
|
75
|
+
* Serves the prior champion first (shadow delay), then evaluates a new candidate.
|
|
76
|
+
*/
|
|
77
|
+
export declare function runFlywheelGeneration(root: string, deps: GenerationDeps): Promise<GenerationResult>;
|
|
78
|
+
export interface DriftCheck {
|
|
79
|
+
checked: boolean;
|
|
80
|
+
rolledBack: boolean;
|
|
81
|
+
reason: string;
|
|
82
|
+
servedScore?: number;
|
|
83
|
+
predecessorScore?: number;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A real deployment-safety check on real (not fabricated) data: the store keeps
|
|
87
|
+
* changing as ruflo is used, so a champion benchmarked at promotion time can
|
|
88
|
+
* DRIFT. Each tick, re-score the currently-SERVED champion against its
|
|
89
|
+
* predecessor on a FRESH harvest of the current store; if it now regresses
|
|
90
|
+
* (self-retrieval OR the human anchor), automatically ROLL BACK the active policy
|
|
91
|
+
* to the predecessor. This is the honest analogue of a live-traffic canary —
|
|
92
|
+
* genuine ongoing measurement + genuine rollback — without fabricating traffic.
|
|
93
|
+
* $0; never throws.
|
|
94
|
+
*/
|
|
95
|
+
export declare function checkServedChampionDrift(root: string, deps: GenerationDeps): Promise<DriftCheck>;
|
|
96
|
+
export interface FlywheelStatus {
|
|
97
|
+
generations: number;
|
|
98
|
+
attempts: number;
|
|
99
|
+
lineage: LineageTelemetry;
|
|
100
|
+
plateau: PlateauReport;
|
|
101
|
+
mutation: MutationStat[];
|
|
102
|
+
axisEffectiveness: AxisStat[];
|
|
103
|
+
served: ServedState;
|
|
104
|
+
champion: {
|
|
105
|
+
config: Record<string, number>;
|
|
106
|
+
hash: string | null;
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** Reconstruct the persisted lineage + telemetry for a status endpoint / CLI. */
|
|
110
|
+
export declare function flywheelStatus(root: string): FlywheelStatus;
|
|
111
|
+
//# sourceMappingURL=harness-flywheel-generations.d.ts.map
|