@hmharness/evolution 0.12.1 → 0.13.3
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/dist/bench.js +16 -0
- package/dist/evolve.js +28 -1
- package/package.json +1 -1
package/dist/bench.js
CHANGED
|
@@ -101,6 +101,22 @@ export async function runBench(home, run) {
|
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
const passed = results.filter((r) => r.pass).length;
|
|
104
|
+
// persist a history record: readiness's "eval-regression-stable" condition
|
|
105
|
+
// reads evolution/benches/*.json - without these records the RL gate's
|
|
106
|
+
// stability condition can never be met (day-20 audit gap)
|
|
107
|
+
try {
|
|
108
|
+
const dir = join(home, 'evolution', 'benches');
|
|
109
|
+
await mkdir(dir, { recursive: true });
|
|
110
|
+
const record = {
|
|
111
|
+
time: new Date().toISOString(),
|
|
112
|
+
total: cases.length,
|
|
113
|
+
passed,
|
|
114
|
+
passRate: cases.length === 0 ? 1 : passed / cases.length,
|
|
115
|
+
results: results.map((r) => ({ name: r.name, pass: r.pass, detail: r.detail.slice(0, 200) })),
|
|
116
|
+
};
|
|
117
|
+
await writeFile(join(dir, `${record.time.replace(/[:.]/g, '-')}.json`), JSON.stringify(record, null, 2) + '\n', 'utf8');
|
|
118
|
+
}
|
|
119
|
+
catch { /* history is best-effort; the run result still returns */ }
|
|
104
120
|
return { results, passRate: cases.length === 0 ? 1 : passed / cases.length };
|
|
105
121
|
}
|
|
106
122
|
/** Starter cases so the evolve gate has a signal from day one. */
|
package/dist/evolve.js
CHANGED
|
@@ -178,6 +178,19 @@ export async function runEvolution(opts) {
|
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
report.proposals = proposals;
|
|
181
|
+
// 4b. Promotion quality floor (day-16 meta-audit): "no regression" alone
|
|
182
|
+
// can promote a candidate that is still terrible when the BASELINE itself
|
|
183
|
+
// is weak (baseline 20% -> candidate 25% passes the old gate). An absolute
|
|
184
|
+
// minimum train pass rate closes that hole. Configurable via
|
|
185
|
+
// config.json evolution.minPassRate (default 0.6).
|
|
186
|
+
let minPassRate = 0.6;
|
|
187
|
+
try {
|
|
188
|
+
const cfg = JSON.parse(await readFile(join(home, 'config.json'), 'utf8'));
|
|
189
|
+
if (typeof cfg.evolution?.minPassRate === 'number' && cfg.evolution.minPassRate >= 0 && cfg.evolution.minPassRate <= 1) {
|
|
190
|
+
minPassRate = cfg.evolution.minPassRate;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch { /* default floor */ }
|
|
181
194
|
// 5. A/B gate each proposal on the train set.
|
|
182
195
|
for (const p of proposals.slice(0, maxProposals)) {
|
|
183
196
|
say(`candidate "${p.name}": drafting + candidate bench`);
|
|
@@ -236,6 +249,20 @@ export async function runEvolution(opts) {
|
|
|
236
249
|
say(` rejected (${regression ? 'regression' : 'lower pass rate'})`);
|
|
237
250
|
continue;
|
|
238
251
|
}
|
|
252
|
+
if (candRate < minPassRate) {
|
|
253
|
+
await deleteDraft(home, p.name);
|
|
254
|
+
await recordParetoEntry(home, { name: p.name, parentInsights: insightIds, rejectedReason: `below quality floor (${candRate.toFixed(2)} < ${minPassRate})`, scores: { train: candRate }, metaModel: provider.model, at: new Date().toISOString() });
|
|
255
|
+
report.outcomes.push({
|
|
256
|
+
name: p.name,
|
|
257
|
+
action: 'rejected',
|
|
258
|
+
reason: `below quality floor: pass rate ${candRate} < min ${minPassRate} (non-regression vs a weak baseline ${baseRate} is not good enough)`,
|
|
259
|
+
baseline: { passRate: baseRate, cases: summary(baseResults) },
|
|
260
|
+
candidate: { passRate: candRate, cases: summary(candResults) },
|
|
261
|
+
lineage: { parentInsights: insightIds, scores: { train: candRate }, metaModel: provider.model, decidedAt: new Date().toISOString() },
|
|
262
|
+
});
|
|
263
|
+
say(` rejected (below quality floor ${minPassRate})`);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
239
266
|
if (costRegressions.length > 0) {
|
|
240
267
|
await deleteDraft(home, p.name);
|
|
241
268
|
await recordParetoEntry(home, { name: p.name, parentInsights: insightIds, rejectedReason: `cost regression on ${costRegressions.join(', ')}`, scores: { train: candRate }, metaModel: provider.model, at: new Date().toISOString() });
|
|
@@ -303,7 +330,7 @@ export async function runEvolution(opts) {
|
|
|
303
330
|
report.outcomes.push({
|
|
304
331
|
name: p.name,
|
|
305
332
|
action: 'promoted',
|
|
306
|
-
reason: `no regression (train ${(candRate * 100).toFixed(0)}% vs ${(baseRate * 100).toFixed(0)}%${holdout.length ? `, holdout ${(holdoutRate * 100).toFixed(0)}%` : ''}) - promoted to CANARY (20% sessions, impact-gated full promotion)${archivedPrevious ? '; previous version archived' : ''}${weakGate ? ' [WEAK GATE: no holdout cases defined]' : ''}`,
|
|
333
|
+
reason: `no regression (train ${(candRate * 100).toFixed(0)}% vs ${(baseRate * 100).toFixed(0)}%${holdout.length ? `, holdout ${(holdoutRate * 100).toFixed(0)}%` : ''}; floor ≥${(minPassRate * 100).toFixed(0)}%) - promoted to CANARY (20% sessions, impact-gated full promotion)${archivedPrevious ? '; previous version archived' : ''}${weakGate ? ' [WEAK GATE: no holdout cases defined]' : ''}`,
|
|
307
334
|
baseline: { passRate: baseRate, cases: summary(baseResults) },
|
|
308
335
|
candidate: { passRate: candRate, cases: summary(candResults) },
|
|
309
336
|
...(holdout.length ? { holdout: { baselineRate: holdoutBaseRate, candidateRate: holdoutRate } } : {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hmharness/evolution",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.3",
|
|
4
4
|
"description": "hmharness evolution subsystem: persistent memory, insight capture, skill library, and the bench that gives evolution its fitness signal. First-class kernel citizen, not a plugin.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|