@hmharness/evolution 0.14.16 → 0.14.19

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.
@@ -0,0 +1,80 @@
1
+ /**
2
+ * @hmharness/evolution - External Task Injection (P0-06)
3
+ *
4
+ * The audit's anti-self-validation requirement: "如果 Agent 自己生成任务、
5
+ * 自己执行、自己评测,就很容易得到自证式 improvement"
6
+ *
7
+ * This module provides:
8
+ * 1. External task intake: real user/external project tasks enter the system
9
+ * 2. Holdout marking: external tasks go to a SEPARATE holdout set
10
+ * 3. Contamination prevention: external holdout tasks NEVER enter training
11
+ * 4. Independence verification: holdout results are measured separately
12
+ */
13
+ export interface ExternalTask {
14
+ /** unique id */
15
+ id: string;
16
+ /** the task prompt (from a real user, not self-generated) */
17
+ prompt: string;
18
+ /** source identifier (e.g. "github-issue", "user-request", "external-benchmark") */
19
+ source: string;
20
+ /** when this task was submitted */
21
+ submittedAt: string;
22
+ /** expected outcome (if known) for verification */
23
+ expectedOutcome?: string;
24
+ /** the agent's actual result (filled after execution) */
25
+ actualResult?: string;
26
+ /** whether this task passed */
27
+ passed?: boolean;
28
+ /** metadata */
29
+ metadata?: Record<string, unknown>;
30
+ }
31
+ export interface HoldoutReport {
32
+ totalExternal: number;
33
+ totalEvaluated: number;
34
+ passRate: number;
35
+ bySource: Record<string, {
36
+ total: number;
37
+ passed: number;
38
+ }>;
39
+ /** contamination check: any external tasks found in training data? */
40
+ contaminationDetected: boolean;
41
+ contaminationCount: number;
42
+ }
43
+ /**
44
+ * Submit an external task into the system.
45
+ * External tasks are marked separately and NEVER enter the training set.
46
+ */
47
+ export declare function submitExternalTask(home: string, task: {
48
+ prompt: string;
49
+ source: string;
50
+ expectedOutcome?: string;
51
+ metadata?: Record<string, unknown>;
52
+ }): Promise<ExternalTask>;
53
+ /**
54
+ * Read all external tasks.
55
+ */
56
+ export declare function readExternalTasks(home: string): Promise<ExternalTask[]>;
57
+ /**
58
+ * CRITICAL: Check that no external holdout tasks have leaked into
59
+ * the training data (DPO pairs). This is the audit's anti-cheating mechanism.
60
+ *
61
+ * Pure - testable.
62
+ */
63
+ export declare function checkContamination(externalTasks: ExternalTask[], trainingPairPrompts: string[]): {
64
+ contaminated: boolean;
65
+ count: number;
66
+ details: string[];
67
+ };
68
+ /**
69
+ * Build a holdout report from external task results.
70
+ * Pure - testable.
71
+ */
72
+ export declare function buildHoldoutReport(tasks: ExternalTask[], contaminationResult: {
73
+ contaminated: boolean;
74
+ count: number;
75
+ }): HoldoutReport;
76
+ /**
77
+ * Generate a summary of external task status.
78
+ * Pure - testable.
79
+ */
80
+ export declare function externalTaskSummary(tasks: ExternalTask[]): string;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @hmharness/evolution - External Task Injection (P0-06)
3
+ *
4
+ * The audit's anti-self-validation requirement: "如果 Agent 自己生成任务、
5
+ * 自己执行、自己评测,就很容易得到自证式 improvement"
6
+ *
7
+ * This module provides:
8
+ * 1. External task intake: real user/external project tasks enter the system
9
+ * 2. Holdout marking: external tasks go to a SEPARATE holdout set
10
+ * 3. Contamination prevention: external holdout tasks NEVER enter training
11
+ * 4. Independence verification: holdout results are measured separately
12
+ */
13
+ import { readFile, appendFile, mkdir } from 'node:fs/promises';
14
+ import { join } from 'node:path';
15
+ const externalFile = (home) => join(home, 'evolution', 'external-tasks.jsonl');
16
+ /**
17
+ * Submit an external task into the system.
18
+ * External tasks are marked separately and NEVER enter the training set.
19
+ */
20
+ export async function submitExternalTask(home, task) {
21
+ const dir = join(home, 'evolution');
22
+ await mkdir(dir, { recursive: true });
23
+ const ext = {
24
+ id: `ext-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`,
25
+ prompt: task.prompt,
26
+ source: task.source,
27
+ submittedAt: new Date().toISOString(),
28
+ expectedOutcome: task.expectedOutcome,
29
+ metadata: task.metadata,
30
+ };
31
+ await appendFile(externalFile(home), JSON.stringify(ext) + '\n', 'utf8');
32
+ return ext;
33
+ }
34
+ /**
35
+ * Read all external tasks.
36
+ */
37
+ export async function readExternalTasks(home) {
38
+ try {
39
+ const text = await readFile(externalFile(home), 'utf8');
40
+ return text.split('\n').filter(Boolean).map(l => JSON.parse(l));
41
+ }
42
+ catch {
43
+ return [];
44
+ }
45
+ }
46
+ /**
47
+ * CRITICAL: Check that no external holdout tasks have leaked into
48
+ * the training data (DPO pairs). This is the audit's anti-cheating mechanism.
49
+ *
50
+ * Pure - testable.
51
+ */
52
+ export function checkContamination(externalTasks, trainingPairPrompts) {
53
+ const externalPrompts = new Set(externalTasks.map(t => t.prompt.trim().toLowerCase()));
54
+ const details = [];
55
+ let count = 0;
56
+ for (const prompt of trainingPairPrompts) {
57
+ const normalized = prompt.trim().toLowerCase();
58
+ // exact match or high overlap (first 100 chars)
59
+ const prefix = normalized.slice(0, 100);
60
+ if (externalPrompts.has(normalized) || [...externalPrompts].some(ep => ep.startsWith(prefix))) {
61
+ count++;
62
+ details.push(`training prompt matches external task: "${prompt.slice(0, 60)}..."`);
63
+ }
64
+ }
65
+ return { contaminated: count > 0, count, details };
66
+ }
67
+ /**
68
+ * Build a holdout report from external task results.
69
+ * Pure - testable.
70
+ */
71
+ export function buildHoldoutReport(tasks, contaminationResult) {
72
+ const evaluated = tasks.filter(t => t.passed !== undefined);
73
+ const passed = evaluated.filter(t => t.passed).length;
74
+ const bySource = {};
75
+ for (const t of evaluated) {
76
+ const src = t.source || 'unknown';
77
+ if (!bySource[src])
78
+ bySource[src] = { total: 0, passed: 0 };
79
+ bySource[src].total++;
80
+ if (t.passed)
81
+ bySource[src].passed++;
82
+ }
83
+ return {
84
+ totalExternal: tasks.length,
85
+ totalEvaluated: evaluated.length,
86
+ passRate: evaluated.length > 0 ? passed / evaluated.length : 0,
87
+ bySource,
88
+ contaminationDetected: contaminationResult.contaminated,
89
+ contaminationCount: contaminationResult.count,
90
+ };
91
+ }
92
+ /**
93
+ * Generate a summary of external task status.
94
+ * Pure - testable.
95
+ */
96
+ export function externalTaskSummary(tasks) {
97
+ const pending = tasks.filter(t => t.passed === undefined).length;
98
+ const evaluated = tasks.filter(t => t.passed !== undefined).length;
99
+ const passed = tasks.filter(t => t.passed === true).length;
100
+ const sources = [...new Set(tasks.map(t => t.source))];
101
+ return `external tasks: ${tasks.length} total, ${pending} pending, ${evaluated} evaluated (${passed} passed), sources: ${sources.join(', ') || 'none'}`;
102
+ }
package/dist/index.d.ts CHANGED
@@ -17,3 +17,5 @@ export * from './impact.ts';
17
17
  export * from './workflows.ts';
18
18
  export * from './knowledge.ts';
19
19
  export * from './radar.ts';
20
+ export * from './external.ts';
21
+ export * from './knowledge-graph.ts';
package/dist/index.js CHANGED
@@ -17,3 +17,5 @@ export * from "./impact.js";
17
17
  export * from "./workflows.js";
18
18
  export * from "./knowledge.js";
19
19
  export * from "./radar.js";
20
+ export * from "./external.js";
21
+ export * from "./knowledge-graph.js";
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hmharness/evolution",
3
- "version": "0.14.16",
3
+ "version": "0.14.19",
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",
@@ -15,7 +15,7 @@
15
15
  "build": "tsc -p tsconfig.build.json"
16
16
  },
17
17
  "dependencies": {
18
- "@hmharness/kernel": "0.9.3"
18
+ "@hmharness/kernel": "0.9.4"
19
19
  },
20
20
  "files": [
21
21
  "dist"