@hmharness/evolution 0.14.1 → 0.14.5

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.d.ts CHANGED
@@ -24,6 +24,12 @@ export interface BenchResult {
24
24
  detail: string;
25
25
  }
26
26
  export declare function matchExpect(output: string, expect: string[]): boolean;
27
+ /** Fence convention: when the reply is a fenced block, assertions test the
28
+ * fence INNER content (day-55 finding: models preserve literals verbatim
29
+ * inside code fences while normalizing them in prose - the fence is the
30
+ * reply convention that makes exactness attainable for habit-prone models).
31
+ * Tolerates a trailing space after the opening fence markers. */
32
+ export declare function fenceInner(output: string): string;
27
33
  /** Full structured assertion: every declared mode must hold. */
28
34
  export declare function matchCase(output: string, c: Pick<BenchCase, 'expect' | 'expectExact' | 'expectRegex' | 'expectNone' | 'expectAny'>): {
29
35
  pass: boolean;
package/dist/bench.js CHANGED
@@ -24,27 +24,37 @@ export function matchExpect(output, expect) {
24
24
  const lower = output.toLowerCase();
25
25
  return expect.every((e) => lower.includes(e.toLowerCase()));
26
26
  }
27
+ /** Fence convention: when the reply is a fenced block, assertions test the
28
+ * fence INNER content (day-55 finding: models preserve literals verbatim
29
+ * inside code fences while normalizing them in prose - the fence is the
30
+ * reply convention that makes exactness attainable for habit-prone models).
31
+ * Tolerates a trailing space after the opening fence markers. */
32
+ export function fenceInner(output) {
33
+ const m = output.match(/```[a-zA-Z]*[ \t]*\r?\n([\s\S]*?)(?:\r?\n)?```/);
34
+ return m ? m[1] : output;
35
+ }
27
36
  /** Full structured assertion: every declared mode must hold. */
28
37
  export function matchCase(output, c) {
29
- if (c.expectExact !== undefined && output.trim() !== c.expectExact.trim()) {
30
- return { pass: false, detail: `exact mismatch: got "${output.trim().slice(0, 80)}"` };
38
+ const body = fenceInner(output);
39
+ if (c.expectExact !== undefined && body.trim() !== c.expectExact.trim()) {
40
+ return { pass: false, detail: `exact mismatch: got "${body.trim().slice(0, 80)}"` };
31
41
  }
32
42
  if (c.expectRegex !== undefined) {
33
43
  try {
34
- if (!new RegExp(c.expectRegex).test(output))
44
+ if (!new RegExp(c.expectRegex).test(body))
35
45
  return { pass: false, detail: `regex mismatch: /${c.expectRegex.slice(0, 60)}/` };
36
46
  }
37
47
  catch {
38
48
  return { pass: false, detail: `invalid regex in case: ${c.expectRegex.slice(0, 40)}` };
39
49
  }
40
50
  }
41
- if (c.expectNone && c.expectNone.some((e) => output.toLowerCase().includes(e.toLowerCase()))) {
51
+ if (c.expectNone && c.expectNone.some((e) => body.toLowerCase().includes(e.toLowerCase()))) {
42
52
  return { pass: false, detail: `forbidden marker present: ${c.expectNone.join(' && ')}` };
43
53
  }
44
- if (c.expectAny && !c.expectAny.some((e) => output.toLowerCase().includes(e.toLowerCase()))) {
54
+ if (c.expectAny && !c.expectAny.some((e) => body.toLowerCase().includes(e.toLowerCase()))) {
45
55
  return { pass: false, detail: `none of the allowed markers found: ${c.expectAny.join(' || ')}` };
46
56
  }
47
- if (!matchExpect(output, c.expect)) {
57
+ if (!matchExpect(body, c.expect)) {
48
58
  return { pass: false, detail: `missing "${c.expect.join('" && "')}" in output` };
49
59
  }
50
60
  return { pass: true, detail: 'ok' };
@@ -46,6 +46,12 @@ export interface ExperimentArm {
46
46
  n: number;
47
47
  passRate: number;
48
48
  tokens: number;
49
+ /** per-case rows (day-56): which case flipped is the point of a diff */
50
+ results?: Array<{
51
+ name: string;
52
+ pass: boolean;
53
+ tokens: number;
54
+ }>;
49
55
  }
50
56
  export interface ExperimentReport {
51
57
  candidateId: string;
@@ -105,23 +105,29 @@ export async function runCandidateExperiment(home, candidateId, opts) {
105
105
  // holdout cases are excluded from promotion gates (bench.ts anti-memorization)
106
106
  const gate = opts.cases.filter((c) => !c.holdout).slice(0, Math.max(1, opts.maxCases ?? 12));
107
107
  let cPass = 0, cTok = 0, tPass = 0, tTok = 0;
108
+ // per-case rows (day-56 observability: which case flipped is the whole
109
+ // point of a diff - aggregates alone cannot answer it)
110
+ const ctlRows = [];
111
+ const trtRows = [];
108
112
  for (const c of gate) {
109
113
  const ctl = await opts.runCase(c, 'control');
110
114
  if (ctl.pass)
111
115
  cPass++;
112
116
  cTok += ctl.tokens;
117
+ ctlRows.push({ name: c.name, pass: ctl.pass, tokens: ctl.tokens });
113
118
  const trt = await opts.runCase(c, 'treatment');
114
119
  if (trt.pass)
115
120
  tPass++;
116
121
  tTok += trt.tokens;
122
+ trtRows.push({ name: c.name, pass: trt.pass, tokens: trt.tokens });
117
123
  }
118
124
  const v = verdictFor({ pass: cPass, n: gate.length }, { pass: tPass, n: gate.length });
119
125
  const report = {
120
126
  candidateId,
121
127
  ranAt: new Date().toISOString(),
122
128
  cases: gate.length,
123
- control: { pass: cPass, n: gate.length, passRate: gate.length ? cPass / gate.length : 0, tokens: cTok },
124
- treatment: { pass: tPass, n: gate.length, passRate: gate.length ? tPass / gate.length : 0, tokens: tTok },
129
+ control: { pass: cPass, n: gate.length, passRate: gate.length ? cPass / gate.length : 0, tokens: cTok, results: ctlRows },
130
+ treatment: { pass: tPass, n: gate.length, passRate: gate.length ? tPass / gate.length : 0, tokens: tTok, results: trtRows },
125
131
  ...v,
126
132
  };
127
133
  const dir = join(home, 'evolution', 'experiments', candidateId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hmharness/evolution",
3
- "version": "0.14.1",
3
+ "version": "0.14.5",
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",