@hmharness/evaluation 0.8.2 → 0.8.4

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/index.d.ts CHANGED
@@ -2,3 +2,5 @@ export * from './types.ts';
2
2
  export * from './evaluators.ts';
3
3
  export * from './runner.ts';
4
4
  export * from './harmonybench.ts';
5
+ export * from './score.ts';
6
+ export * from './sdk.ts';
package/dist/index.js CHANGED
@@ -2,3 +2,5 @@ export * from "./types.js";
2
2
  export * from "./evaluators.js";
3
3
  export * from "./runner.js";
4
4
  export * from "./harmonybench.js";
5
+ export * from "./score.js";
6
+ export * from "./sdk.js";
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @hmharness/evaluation - HMH Score 1.0 (P1-03)
3
+ *
4
+ * The audit called for: "统一 0-100 多维总分体系" with dimensions:
5
+ * correctness, test, reliability, repair, security, cost, latency,
6
+ * human, maintainability.
7
+ *
8
+ * HMH Score is the single number that tracks whether the harness is
9
+ * actually getting better. Every dimension maps to [0,100]; the composite
10
+ * is a weighted mean. Weights are versioned (changing weights = version bump).
11
+ */
12
+ export declare const HMH_SCORE_VERSION = "1.0.0";
13
+ export type ScoreDimension = 'correctness' | 'test' | 'reliability' | 'repair' | 'security' | 'cost' | 'latency' | 'human' | 'maintainability';
14
+ /** All dimensions in canonical order */
15
+ export declare const ALL_DIMENSIONS: ScoreDimension[];
16
+ /** Default weights (sum to 1.0) - versioned, changing requires bump */
17
+ export declare const DEFAULT_WEIGHTS: Record<ScoreDimension, number>;
18
+ export interface DimensionScore {
19
+ dimension: ScoreDimension;
20
+ /** raw score [0,100] */
21
+ score: number;
22
+ /** how this score was computed (for audit) */
23
+ method: string;
24
+ /** sample size used to compute this score */
25
+ sampleSize: number;
26
+ }
27
+ export interface HMHScore {
28
+ version: string;
29
+ /** composite score [0,100] */
30
+ composite: number;
31
+ /** per-dimension breakdown */
32
+ dimensions: DimensionScore[];
33
+ /** when this score was computed */
34
+ computedAt: string;
35
+ /** any warnings about data quality */
36
+ warnings: string[];
37
+ }
38
+ /**
39
+ * Compute the composite HMH Score from dimension scores.
40
+ * Pure - testable.
41
+ */
42
+ export declare function computeHMHScore(dimensions: DimensionScore[], weights?: Record<ScoreDimension, number>): HMHScore;
43
+ /**
44
+ * Compute correctness from pass rate.
45
+ * Pure - testable.
46
+ */
47
+ export declare function correctnessFromPassRate(passRate: number, sampleSize: number): DimensionScore;
48
+ /**
49
+ * Compute test coverage score.
50
+ * Pure - testable.
51
+ */
52
+ export declare function testFromCoverage(coveragePercent: number): DimensionScore;
53
+ /**
54
+ * Compute reliability from success rate.
55
+ * Pure - testable.
56
+ */
57
+ export declare function reliabilityFromSuccessRate(successRate: number, sessions: number): DimensionScore;
58
+ /**
59
+ * Compute security score from red-team results.
60
+ * Pure - testable.
61
+ */
62
+ export declare function securityFromRedTeam(blocked: number, total: number): DimensionScore;
63
+ /**
64
+ * Compute cost efficiency (inverse of cost, normalized).
65
+ * Pure - testable.
66
+ */
67
+ export declare function costFromTokensPerTask(avgTokens: number, budgetTokens: number): DimensionScore;
68
+ /**
69
+ * Compute latency score (inverse of duration, normalized).
70
+ * Pure - testable.
71
+ */
72
+ export declare function latencyFromDuration(avgMs: number, budgetMs: number): DimensionScore;
73
+ /**
74
+ * Compute human satisfaction from judge scores.
75
+ * Pure - testable.
76
+ */
77
+ export declare function humanFromJudgeScores(scores: number[]): DimensionScore;
78
+ /**
79
+ * Format an HMH Score as a human-readable string.
80
+ * Pure - testable.
81
+ */
82
+ export declare function formatHMHScore(score: HMHScore): string;
package/dist/score.js ADDED
@@ -0,0 +1,168 @@
1
+ /**
2
+ * @hmharness/evaluation - HMH Score 1.0 (P1-03)
3
+ *
4
+ * The audit called for: "统一 0-100 多维总分体系" with dimensions:
5
+ * correctness, test, reliability, repair, security, cost, latency,
6
+ * human, maintainability.
7
+ *
8
+ * HMH Score is the single number that tracks whether the harness is
9
+ * actually getting better. Every dimension maps to [0,100]; the composite
10
+ * is a weighted mean. Weights are versioned (changing weights = version bump).
11
+ */
12
+ export const HMH_SCORE_VERSION = '1.0.0';
13
+ /** All dimensions in canonical order */
14
+ export const ALL_DIMENSIONS = [
15
+ 'correctness', 'test', 'reliability', 'repair', 'security',
16
+ 'cost', 'latency', 'human', 'maintainability',
17
+ ];
18
+ /** Default weights (sum to 1.0) - versioned, changing requires bump */
19
+ export const DEFAULT_WEIGHTS = {
20
+ correctness: 0.25,
21
+ test: 0.15,
22
+ reliability: 0.12,
23
+ repair: 0.08,
24
+ security: 0.10,
25
+ cost: 0.08,
26
+ latency: 0.07,
27
+ human: 0.10,
28
+ maintainability: 0.05,
29
+ };
30
+ /**
31
+ * Compute the composite HMH Score from dimension scores.
32
+ * Pure - testable.
33
+ */
34
+ export function computeHMHScore(dimensions, weights = DEFAULT_WEIGHTS) {
35
+ const warnings = [];
36
+ const dimMap = new Map(dimensions.map(d => [d.dimension, d]));
37
+ const missing = ALL_DIMENSIONS.filter(d => !dimMap.has(d));
38
+ if (missing.length > 0)
39
+ warnings.push(`missing dimensions: ${missing.join(', ')} (treated as 0)`);
40
+ let composite = 0;
41
+ let totalWeight = 0;
42
+ for (const dim of ALL_DIMENSIONS) {
43
+ const ds = dimMap.get(dim);
44
+ const w = weights[dim] ?? 0;
45
+ if (ds) {
46
+ composite += Math.max(0, Math.min(100, ds.score)) * w;
47
+ totalWeight += w;
48
+ }
49
+ }
50
+ if (totalWeight > 0)
51
+ composite = composite / totalWeight;
52
+ return {
53
+ version: HMH_SCORE_VERSION,
54
+ composite: Math.round(composite * 100) / 100,
55
+ dimensions,
56
+ computedAt: new Date().toISOString(),
57
+ warnings,
58
+ };
59
+ }
60
+ /**
61
+ * Compute correctness from pass rate.
62
+ * Pure - testable.
63
+ */
64
+ export function correctnessFromPassRate(passRate, sampleSize) {
65
+ return {
66
+ dimension: 'correctness',
67
+ score: Math.round(passRate * 100),
68
+ method: `pass rate × 100 (${sampleSize} samples)`,
69
+ sampleSize,
70
+ };
71
+ }
72
+ /**
73
+ * Compute test coverage score.
74
+ * Pure - testable.
75
+ */
76
+ export function testFromCoverage(coveragePercent) {
77
+ return {
78
+ dimension: 'test',
79
+ score: Math.round(Math.min(100, coveragePercent)),
80
+ method: `test coverage % (${coveragePercent.toFixed(1)}%)`,
81
+ sampleSize: 1,
82
+ };
83
+ }
84
+ /**
85
+ * Compute reliability from success rate.
86
+ * Pure - testable.
87
+ */
88
+ export function reliabilityFromSuccessRate(successRate, sessions) {
89
+ return {
90
+ dimension: 'reliability',
91
+ score: Math.round(successRate * 100),
92
+ method: `session success rate × 100 (${sessions} sessions)`,
93
+ sampleSize: sessions,
94
+ };
95
+ }
96
+ /**
97
+ * Compute security score from red-team results.
98
+ * Pure - testable.
99
+ */
100
+ export function securityFromRedTeam(blocked, total) {
101
+ const rate = total > 0 ? blocked / total : 0;
102
+ return {
103
+ dimension: 'security',
104
+ score: Math.round(rate * 100),
105
+ method: `red-team block rate (${blocked}/${total} attacks blocked)`,
106
+ sampleSize: total,
107
+ };
108
+ }
109
+ /**
110
+ * Compute cost efficiency (inverse of cost, normalized).
111
+ * Pure - testable.
112
+ */
113
+ export function costFromTokensPerTask(avgTokens, budgetTokens) {
114
+ const ratio = budgetTokens > 0 ? avgTokens / budgetTokens : 1;
115
+ const score = Math.round(Math.max(0, Math.min(100, (1 - ratio) * 100)));
116
+ return {
117
+ dimension: 'cost',
118
+ score,
119
+ method: `token efficiency (avg ${avgTokens} / budget ${budgetTokens})`,
120
+ sampleSize: 1,
121
+ };
122
+ }
123
+ /**
124
+ * Compute latency score (inverse of duration, normalized).
125
+ * Pure - testable.
126
+ */
127
+ export function latencyFromDuration(avgMs, budgetMs) {
128
+ const ratio = budgetMs > 0 ? avgMs / budgetMs : 1;
129
+ const score = Math.round(Math.max(0, Math.min(100, (1 - ratio) * 100)));
130
+ return {
131
+ dimension: 'latency',
132
+ score,
133
+ method: `latency efficiency (avg ${avgMs}ms / budget ${budgetMs}ms)`,
134
+ sampleSize: 1,
135
+ };
136
+ }
137
+ /**
138
+ * Compute human satisfaction from judge scores.
139
+ * Pure - testable.
140
+ */
141
+ export function humanFromJudgeScores(scores) {
142
+ if (scores.length === 0) {
143
+ return { dimension: 'human', score: 0, method: 'no judge scores', sampleSize: 0 };
144
+ }
145
+ const avg = scores.reduce((a, b) => a + b, 0) / scores.length;
146
+ return {
147
+ dimension: 'human',
148
+ score: Math.round((avg / 5) * 100), // 5-point scale → 0-100
149
+ method: `avg judge score (${avg.toFixed(2)}/5 across ${scores.length} sessions)`,
150
+ sampleSize: scores.length,
151
+ };
152
+ }
153
+ /**
154
+ * Format an HMH Score as a human-readable string.
155
+ * Pure - testable.
156
+ */
157
+ export function formatHMHScore(score) {
158
+ const lines = [`HMH Score v${score.version}: ${score.composite}/100`];
159
+ const sorted = [...score.dimensions].sort((a, b) => b.score - a.score);
160
+ for (const d of sorted) {
161
+ const bar = '█'.repeat(Math.round(d.score / 10)) + '░'.repeat(10 - Math.round(d.score / 10));
162
+ lines.push(` ${d.dimension.padEnd(16)} ${bar} ${String(d.score).padStart(3)} (${d.method})`);
163
+ }
164
+ if (score.warnings.length > 0) {
165
+ lines.push(` ⚠ ${score.warnings.join('; ')}`);
166
+ }
167
+ return lines.join('\n');
168
+ }
package/dist/sdk.d.ts ADDED
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @hmharness/evaluation - Evaluator SDK (P1-02)
3
+ *
4
+ * The audit called for: "统一 evaluator plugin、evidence references、metric registry"
5
+ *
6
+ * This module provides the plugin interface that ALL evaluators implement,
7
+ * evidence references that link scores to concrete proof, and a metric
8
+ * registry for named, versioned scoring functions.
9
+ */
10
+ /** Evidence tier per the existing evidence ladder (M2) */
11
+ export type EvidenceTier = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
12
+ /** Reference to concrete evidence backing an evaluation */
13
+ export interface EvidenceRef {
14
+ /** what kind of evidence */
15
+ kind: 'build-log' | 'test-result' | 'command-output' | 'file-content' | 'judge-score' | 'device-log' | 'metric';
16
+ /** where to find it (path, URL, or inline) */
17
+ source: string;
18
+ /** relevant excerpt (for audit trail) */
19
+ excerpt?: string;
20
+ /** evidence tier (1=build, 7=llm-judge, 8=self-report) */
21
+ tier: EvidenceTier;
22
+ }
23
+ /** The result of running an evaluator */
24
+ export interface SdkEvaluationResult {
25
+ /** evaluator that produced this result */
26
+ evaluatorId: string;
27
+ /** pass/fail/indeterminate */
28
+ outcome: 'pass' | 'fail' | 'indeterminate';
29
+ /** numeric score [0,1] if applicable */
30
+ score?: number;
31
+ /** human-readable detail */
32
+ detail: string;
33
+ /** evidence backing this result */
34
+ evidence: EvidenceRef[];
35
+ /** when this evaluation ran */
36
+ evaluatedAt: string;
37
+ /** duration in ms */
38
+ durationMs: number;
39
+ }
40
+ /** The evaluator plugin interface - ALL evaluators implement this */
41
+ export interface SdkEvaluator {
42
+ /** unique evaluator id */
43
+ readonly id: string;
44
+ /** what this evaluator checks */
45
+ readonly description: string;
46
+ /** evidence tier this evaluator operates at */
47
+ readonly tier: EvidenceTier;
48
+ /** evaluate and return a result */
49
+ evaluate(input: unknown): Promise<SdkEvaluationResult>;
50
+ }
51
+ /** A named metric in the registry */
52
+ export interface MetricDef {
53
+ name: string;
54
+ description: string;
55
+ /** units (e.g. 'percent', 'count', 'ms') */
56
+ unit: string;
57
+ /** higher is better? (for display) */
58
+ higherIsBetter: boolean;
59
+ /** compute from evaluation results */
60
+ compute: (results: SdkEvaluationResult[]) => number;
61
+ }
62
+ /**
63
+ * The metric registry - named, versioned scoring functions.
64
+ * Metrics are registered once and referenced by name everywhere.
65
+ */
66
+ export declare class MetricRegistry {
67
+ private metrics;
68
+ register(def: MetricDef): {
69
+ ok: boolean;
70
+ reason?: string;
71
+ };
72
+ get(name: string): MetricDef | undefined;
73
+ list(): MetricDef[];
74
+ /** Compute a metric from evaluation results */
75
+ compute(name: string, results: SdkEvaluationResult[]): number | undefined;
76
+ }
77
+ /** Create a standard metric registry with common metrics pre-registered */
78
+ export declare function createDefaultRegistry(): MetricRegistry;
79
+ /**
80
+ * Create a simple pass/fail evaluator from a predicate function.
81
+ * Convenience factory for the most common evaluator pattern.
82
+ */
83
+ export declare function createPredicateEvaluator(id: string, description: string, tier: EvidenceTier, predicate: (input: unknown) => boolean, evidenceKind?: EvidenceRef['kind']): SdkEvaluator;
package/dist/sdk.js ADDED
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @hmharness/evaluation - Evaluator SDK (P1-02)
3
+ *
4
+ * The audit called for: "统一 evaluator plugin、evidence references、metric registry"
5
+ *
6
+ * This module provides the plugin interface that ALL evaluators implement,
7
+ * evidence references that link scores to concrete proof, and a metric
8
+ * registry for named, versioned scoring functions.
9
+ */
10
+ /**
11
+ * The metric registry - named, versioned scoring functions.
12
+ * Metrics are registered once and referenced by name everywhere.
13
+ */
14
+ export class MetricRegistry {
15
+ metrics = new Map();
16
+ register(def) {
17
+ if (this.metrics.has(def.name)) {
18
+ return { ok: false, reason: `metric ${def.name} already registered` };
19
+ }
20
+ this.metrics.set(def.name, def);
21
+ return { ok: true };
22
+ }
23
+ get(name) {
24
+ return this.metrics.get(name);
25
+ }
26
+ list() {
27
+ return [...this.metrics.values()];
28
+ }
29
+ /** Compute a metric from evaluation results */
30
+ compute(name, results) {
31
+ const m = this.metrics.get(name);
32
+ if (!m)
33
+ return undefined;
34
+ return m.compute(results);
35
+ }
36
+ }
37
+ /** Create a standard metric registry with common metrics pre-registered */
38
+ export function createDefaultRegistry() {
39
+ const reg = new MetricRegistry();
40
+ reg.register({
41
+ name: 'pass_rate', description: 'fraction of evaluations that passed',
42
+ unit: 'fraction', higherIsBetter: true,
43
+ compute: (rs) => rs.length > 0 ? rs.filter(r => r.outcome === 'pass').length / rs.length : 0,
44
+ });
45
+ reg.register({
46
+ name: 'avg_score', description: 'average numeric score',
47
+ unit: 'fraction', higherIsBetter: true,
48
+ compute: (rs) => {
49
+ const scored = rs.filter(r => r.score !== undefined);
50
+ return scored.length > 0 ? scored.reduce((a, b) => a + (b.score ?? 0), 0) / scored.length : 0;
51
+ },
52
+ });
53
+ reg.register({
54
+ name: 'avg_duration', description: 'average evaluation duration',
55
+ unit: 'ms', higherIsBetter: false,
56
+ compute: (rs) => rs.length > 0 ? rs.reduce((a, b) => a + b.durationMs, 0) / rs.length : 0,
57
+ });
58
+ reg.register({
59
+ name: 'evidence_coverage', description: 'fraction of results with evidence',
60
+ unit: 'fraction', higherIsBetter: true,
61
+ compute: (rs) => rs.length > 0 ? rs.filter(r => r.evidence.length > 0).length / rs.length : 0,
62
+ });
63
+ return reg;
64
+ }
65
+ /**
66
+ * Create a simple pass/fail evaluator from a predicate function.
67
+ * Convenience factory for the most common evaluator pattern.
68
+ */
69
+ export function createPredicateEvaluator(id, description, tier, predicate, evidenceKind = 'command-output') {
70
+ return {
71
+ id, description, tier,
72
+ async evaluate(input) {
73
+ const start = Date.now();
74
+ const pass = predicate(input);
75
+ return {
76
+ evaluatorId: id,
77
+ outcome: pass ? 'pass' : 'fail',
78
+ detail: pass ? 'predicate satisfied' : 'predicate not satisfied',
79
+ evidence: [{ kind: evidenceKind, source: 'inline', tier, excerpt: String(input).slice(0, 200) }],
80
+ evaluatedAt: new Date().toISOString(),
81
+ durationMs: Date.now() - start,
82
+ };
83
+ },
84
+ };
85
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hmharness/evaluation",
3
- "version": "0.8.2",
3
+ "version": "0.8.4",
4
4
  "description": "hmharness evaluation: the Evaluator/Judge contract (V2 blueprint M2). Hard evidence outranks LLM judgment - build results, exit codes, exact/regex assertions first; the LLM judge is a last resort and is labeled as such. Evaluations attach to trajectories (judge.completed events).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",