@hmharness/evolution 0.14.18 → 0.14.20
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 +2 -0
- package/dist/index.js +2 -0
- package/dist/knowledge-graph.d.ts +47 -0
- package/dist/knowledge-graph.js +85 -0
- package/dist/router-evolution.d.ts +60 -0
- package/dist/router-evolution.js +67 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hmharness/evolution - Project Knowledge Graph (P1-04)
|
|
3
|
+
*
|
|
4
|
+
* The audit called for: "Project/Module/API/Bug/Decision/Skill/SDK/Device 关系"
|
|
5
|
+
*
|
|
6
|
+
* A lightweight in-memory graph with typed nodes and edges, persisted as JSONL.
|
|
7
|
+
* Query by node type, edge type, or traversal.
|
|
8
|
+
*/
|
|
9
|
+
export type KGNodeType = 'project' | 'module' | 'api' | 'bug' | 'decision' | 'skill' | 'sdk' | 'device' | 'person';
|
|
10
|
+
export type KGEdgeType = 'contains' | 'depends_on' | 'uses' | 'fixes' | 'decided_by' | 'built_with' | 'runs_on' | 'authored_by' | 'relates_to';
|
|
11
|
+
export interface KGNode {
|
|
12
|
+
id: string;
|
|
13
|
+
type: KGNodeType;
|
|
14
|
+
label: string;
|
|
15
|
+
properties?: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
export interface KGEdge {
|
|
18
|
+
from: string;
|
|
19
|
+
to: string;
|
|
20
|
+
type: KGEdgeType;
|
|
21
|
+
properties?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
export interface KnowledgeGraph {
|
|
24
|
+
nodes: KGNode[];
|
|
25
|
+
edges: KGEdge[];
|
|
26
|
+
}
|
|
27
|
+
export declare function emptyGraph(): KnowledgeGraph;
|
|
28
|
+
export declare function addNode(g: KnowledgeGraph, node: KGNode): KnowledgeGraph;
|
|
29
|
+
export declare function addEdge(g: KnowledgeGraph, edge: KGEdge): KnowledgeGraph;
|
|
30
|
+
export declare function findNode(g: KnowledgeGraph, id: string): KGNode | undefined;
|
|
31
|
+
export declare function nodesByType(g: KnowledgeGraph, type: KGNodeType): KGNode[];
|
|
32
|
+
export declare function edgesByType(g: KnowledgeGraph, type: KGEdgeType): KGEdge[];
|
|
33
|
+
/** Get all nodes connected to a given node (either direction) */
|
|
34
|
+
export declare function neighbors(g: KnowledgeGraph, nodeId: string): KGNode[];
|
|
35
|
+
/** Get all nodes reachable from a starting node via specific edge types */
|
|
36
|
+
export declare function traverse(g: KnowledgeGraph, startId: string, edgeTypes: KGEdgeType[]): KGNode[];
|
|
37
|
+
/** Serialize to JSONL for persistence */
|
|
38
|
+
export declare function tojsonl(g: KnowledgeGraph): string;
|
|
39
|
+
/** Deserialize from JSONL */
|
|
40
|
+
export declare function fromjsonl(text: string): KnowledgeGraph;
|
|
41
|
+
/** Count nodes and edges by type for a summary */
|
|
42
|
+
export declare function graphStats(g: KnowledgeGraph): {
|
|
43
|
+
nodes: number;
|
|
44
|
+
edges: number;
|
|
45
|
+
byNodeType: Record<string, number>;
|
|
46
|
+
byEdgeType: Record<string, number>;
|
|
47
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hmharness/evolution - Project Knowledge Graph (P1-04)
|
|
3
|
+
*
|
|
4
|
+
* The audit called for: "Project/Module/API/Bug/Decision/Skill/SDK/Device 关系"
|
|
5
|
+
*
|
|
6
|
+
* A lightweight in-memory graph with typed nodes and edges, persisted as JSONL.
|
|
7
|
+
* Query by node type, edge type, or traversal.
|
|
8
|
+
*/
|
|
9
|
+
export function emptyGraph() {
|
|
10
|
+
return { nodes: [], edges: [] };
|
|
11
|
+
}
|
|
12
|
+
export function addNode(g, node) {
|
|
13
|
+
if (g.nodes.some(n => n.id === node.id))
|
|
14
|
+
return g;
|
|
15
|
+
return { ...g, nodes: [...g.nodes, node] };
|
|
16
|
+
}
|
|
17
|
+
export function addEdge(g, edge) {
|
|
18
|
+
return { ...g, edges: [...g.edges, edge] };
|
|
19
|
+
}
|
|
20
|
+
export function findNode(g, id) {
|
|
21
|
+
return g.nodes.find(n => n.id === id);
|
|
22
|
+
}
|
|
23
|
+
export function nodesByType(g, type) {
|
|
24
|
+
return g.nodes.filter(n => n.type === type);
|
|
25
|
+
}
|
|
26
|
+
export function edgesByType(g, type) {
|
|
27
|
+
return g.edges.filter(e => e.type === type);
|
|
28
|
+
}
|
|
29
|
+
/** Get all nodes connected to a given node (either direction) */
|
|
30
|
+
export function neighbors(g, nodeId) {
|
|
31
|
+
const ids = new Set();
|
|
32
|
+
for (const e of g.edges) {
|
|
33
|
+
if (e.from === nodeId)
|
|
34
|
+
ids.add(e.to);
|
|
35
|
+
if (e.to === nodeId)
|
|
36
|
+
ids.add(e.from);
|
|
37
|
+
}
|
|
38
|
+
return g.nodes.filter(n => ids.has(n.id));
|
|
39
|
+
}
|
|
40
|
+
/** Get all nodes reachable from a starting node via specific edge types */
|
|
41
|
+
export function traverse(g, startId, edgeTypes) {
|
|
42
|
+
const visited = new Set([startId]);
|
|
43
|
+
const queue = [startId];
|
|
44
|
+
while (queue.length > 0) {
|
|
45
|
+
const current = queue.shift();
|
|
46
|
+
for (const e of g.edges) {
|
|
47
|
+
if (e.from === current && edgeTypes.includes(e.type) && !visited.has(e.to)) {
|
|
48
|
+
visited.add(e.to);
|
|
49
|
+
queue.push(e.to);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return g.nodes.filter(n => visited.has(n.id));
|
|
54
|
+
}
|
|
55
|
+
/** Serialize to JSONL for persistence */
|
|
56
|
+
export function tojsonl(g) {
|
|
57
|
+
const lines = g.nodes.map(n => JSON.stringify({ _t: 'node', ...n }));
|
|
58
|
+
lines.push(...g.edges.map(e => JSON.stringify({ _t: 'edge', ...e })));
|
|
59
|
+
return lines.join('\n') + '\n';
|
|
60
|
+
}
|
|
61
|
+
/** Deserialize from JSONL */
|
|
62
|
+
export function fromjsonl(text) {
|
|
63
|
+
const g = emptyGraph();
|
|
64
|
+
for (const line of text.split('\n').filter(Boolean)) {
|
|
65
|
+
try {
|
|
66
|
+
const obj = JSON.parse(line);
|
|
67
|
+
if (obj._t === 'node')
|
|
68
|
+
g.nodes.push(obj);
|
|
69
|
+
else if (obj._t === 'edge')
|
|
70
|
+
g.edges.push(obj);
|
|
71
|
+
}
|
|
72
|
+
catch { /* skip malformed */ }
|
|
73
|
+
}
|
|
74
|
+
return g;
|
|
75
|
+
}
|
|
76
|
+
/** Count nodes and edges by type for a summary */
|
|
77
|
+
export function graphStats(g) {
|
|
78
|
+
const byNodeType = {};
|
|
79
|
+
for (const n of g.nodes)
|
|
80
|
+
byNodeType[n.type] = (byNodeType[n.type] ?? 0) + 1;
|
|
81
|
+
const byEdgeType = {};
|
|
82
|
+
for (const e of g.edges)
|
|
83
|
+
byEdgeType[e.type] = (byEdgeType[e.type] ?? 0) + 1;
|
|
84
|
+
return { nodes: g.nodes.length, edges: g.edges.length, byNodeType, byEdgeType };
|
|
85
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hmharness/evolution - Router Evolution (P2-05)
|
|
3
|
+
*
|
|
4
|
+
* The audit called for: "让 model routing 本身成为可实验、可回滚策略"
|
|
5
|
+
*
|
|
6
|
+
* Makes routing decisions experimentable: a routing policy can be registered
|
|
7
|
+
* as a candidate, A/B tested against the current policy, and promoted or
|
|
8
|
+
* rolled back based on SPRT results.
|
|
9
|
+
*/
|
|
10
|
+
import type { RouteHistoryEntry } from '@hmharness/kernel';
|
|
11
|
+
export interface RoutingPolicy {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
/** the routing function */
|
|
16
|
+
decide: (history: RouteHistoryEntry[], taskType: string, candidates: string[]) => string;
|
|
17
|
+
}
|
|
18
|
+
export interface RoutingExperiment {
|
|
19
|
+
id: string;
|
|
20
|
+
/** the challenger policy */
|
|
21
|
+
challenger: RoutingPolicy;
|
|
22
|
+
/** incumbent policy (or 'default' for current behavior) */
|
|
23
|
+
incumbent: string;
|
|
24
|
+
/** control arm results */
|
|
25
|
+
controlSuccesses: number;
|
|
26
|
+
controlTotal: number;
|
|
27
|
+
/** treatment arm results */
|
|
28
|
+
treatmentSuccesses: number;
|
|
29
|
+
treatmentTotal: number;
|
|
30
|
+
status: 'running' | 'promoted' | 'rolled-back' | 'inconclusive';
|
|
31
|
+
startedAt: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Evaluate a routing experiment using simple SPRT logic.
|
|
35
|
+
* Pure - testable.
|
|
36
|
+
*/
|
|
37
|
+
export declare function evaluateExperiment(exp: RoutingExperiment): {
|
|
38
|
+
decision: 'continue' | 'promote' | 'rollback';
|
|
39
|
+
reason: string;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The routing policy registry - manages candidate and active policies.
|
|
43
|
+
*/
|
|
44
|
+
export declare class RoutingPolicyRegistry {
|
|
45
|
+
private policies;
|
|
46
|
+
private activePolicyId;
|
|
47
|
+
register(policy: RoutingPolicy): {
|
|
48
|
+
ok: boolean;
|
|
49
|
+
reason?: string;
|
|
50
|
+
};
|
|
51
|
+
get(id: string): RoutingPolicy | undefined;
|
|
52
|
+
list(): RoutingPolicy[];
|
|
53
|
+
/** Get the currently active policy */
|
|
54
|
+
getActive(): RoutingPolicy | undefined;
|
|
55
|
+
/** Promote a policy to active (rollback = set back to 'default') */
|
|
56
|
+
promote(policyId: string): boolean;
|
|
57
|
+
/** Rollback to default policy */
|
|
58
|
+
rollback(): void;
|
|
59
|
+
get activeId(): string;
|
|
60
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hmharness/evolution - Router Evolution (P2-05)
|
|
3
|
+
*
|
|
4
|
+
* The audit called for: "让 model routing 本身成为可实验、可回滚策略"
|
|
5
|
+
*
|
|
6
|
+
* Makes routing decisions experimentable: a routing policy can be registered
|
|
7
|
+
* as a candidate, A/B tested against the current policy, and promoted or
|
|
8
|
+
* rolled back based on SPRT results.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Evaluate a routing experiment using simple SPRT logic.
|
|
12
|
+
* Pure - testable.
|
|
13
|
+
*/
|
|
14
|
+
export function evaluateExperiment(exp) {
|
|
15
|
+
const minN = 20; // minimum samples per arm
|
|
16
|
+
if (exp.controlTotal < minN || exp.treatmentTotal < minN) {
|
|
17
|
+
return { decision: 'continue', reason: `insufficient samples (control ${exp.controlTotal}/${minN}, treatment ${exp.treatmentTotal}/${minN})` };
|
|
18
|
+
}
|
|
19
|
+
const controlRate = exp.controlSuccesses / exp.controlTotal;
|
|
20
|
+
const treatmentRate = exp.treatmentSuccesses / exp.treatmentTotal;
|
|
21
|
+
const diff = treatmentRate - controlRate;
|
|
22
|
+
// promote if treatment is 5pp better, rollback if 5pp worse
|
|
23
|
+
if (diff >= 0.05) {
|
|
24
|
+
return { decision: 'promote', reason: `treatment ${treatmentRate.toFixed(2)} vs control ${controlRate.toFixed(2)} (+${(diff * 100).toFixed(1)}pp)` };
|
|
25
|
+
}
|
|
26
|
+
if (diff <= -0.05) {
|
|
27
|
+
return { decision: 'rollback', reason: `treatment ${treatmentRate.toFixed(2)} vs control ${controlRate.toFixed(2)} (${(diff * 100).toFixed(1)}pp)` };
|
|
28
|
+
}
|
|
29
|
+
return { decision: 'continue', reason: `diff ${(diff * 100).toFixed(1)}pp within noise band` };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The routing policy registry - manages candidate and active policies.
|
|
33
|
+
*/
|
|
34
|
+
export class RoutingPolicyRegistry {
|
|
35
|
+
policies = new Map();
|
|
36
|
+
activePolicyId = 'default';
|
|
37
|
+
register(policy) {
|
|
38
|
+
if (this.policies.has(policy.id))
|
|
39
|
+
return { ok: false, reason: `policy ${policy.id} already registered` };
|
|
40
|
+
this.policies.set(policy.id, policy);
|
|
41
|
+
return { ok: true };
|
|
42
|
+
}
|
|
43
|
+
get(id) {
|
|
44
|
+
return this.policies.get(id);
|
|
45
|
+
}
|
|
46
|
+
list() {
|
|
47
|
+
return [...this.policies.values()];
|
|
48
|
+
}
|
|
49
|
+
/** Get the currently active policy */
|
|
50
|
+
getActive() {
|
|
51
|
+
return this.policies.get(this.activePolicyId);
|
|
52
|
+
}
|
|
53
|
+
/** Promote a policy to active (rollback = set back to 'default') */
|
|
54
|
+
promote(policyId) {
|
|
55
|
+
if (!this.policies.has(policyId))
|
|
56
|
+
return false;
|
|
57
|
+
this.activePolicyId = policyId;
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
/** Rollback to default policy */
|
|
61
|
+
rollback() {
|
|
62
|
+
this.activePolicyId = 'default';
|
|
63
|
+
}
|
|
64
|
+
get activeId() {
|
|
65
|
+
return this.activePolicyId;
|
|
66
|
+
}
|
|
67
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hmharness/evolution",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.20",
|
|
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",
|