@hmharness/kernel 0.8.2 → 0.9.0
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 +1 -0
- package/dist/index.js +1 -0
- package/dist/router.d.ts +55 -0
- package/dist/router.js +92 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export interface TaskFeatures {
|
|
2
|
+
/** 0..1 heuristic: length, multi-step verbs, tool-ish nouns */
|
|
3
|
+
complexity: number;
|
|
4
|
+
/** rough script/language signal in the task text */
|
|
5
|
+
language: 'cangjie' | 'arkts' | 'json' | 'shell' | 'other';
|
|
6
|
+
/** domain classification */
|
|
7
|
+
domain: 'harmony' | 'generic';
|
|
8
|
+
/** estimated context need (chars of likely file/lookup work) */
|
|
9
|
+
expectedContext: 'small' | 'medium' | 'large';
|
|
10
|
+
/** free-form budgets the caller may pin (defaults = unconstrained) */
|
|
11
|
+
latencyBudget?: 'low' | 'normal';
|
|
12
|
+
costBudget?: 'low' | 'normal';
|
|
13
|
+
}
|
|
14
|
+
export declare function extractFeatures(task: string): TaskFeatures;
|
|
15
|
+
export interface RouteSuggestion {
|
|
16
|
+
/** the route the config actually uses today (recorded for comparison) */
|
|
17
|
+
actual: string;
|
|
18
|
+
/** what the shadow router would pick, and why */
|
|
19
|
+
suggested: string;
|
|
20
|
+
reason: string;
|
|
21
|
+
features: TaskFeatures;
|
|
22
|
+
}
|
|
23
|
+
/** Pure suggestion: harmony-domain heavy tasks want the domain-tuned route;
|
|
24
|
+
* huge-context tasks want the long-window model; everything else keeps the
|
|
25
|
+
* configured default. The caller supplies the route universe. */
|
|
26
|
+
export declare function routeDecision(features: TaskFeatures, opts: {
|
|
27
|
+
actual: string;
|
|
28
|
+
harmonyRoute?: string;
|
|
29
|
+
heavyRoute?: string;
|
|
30
|
+
defaultRoute?: string;
|
|
31
|
+
}): RouteSuggestion;
|
|
32
|
+
/** One routing.outcome row (append-only, redaction is the caller's duty). */
|
|
33
|
+
export interface RoutingOutcome {
|
|
34
|
+
time: string;
|
|
35
|
+
runId?: string;
|
|
36
|
+
task: string;
|
|
37
|
+
features: TaskFeatures;
|
|
38
|
+
actual: string;
|
|
39
|
+
suggested: string;
|
|
40
|
+
reason: string;
|
|
41
|
+
/** filled after the run: did the actual route succeed? */
|
|
42
|
+
outcome?: string;
|
|
43
|
+
tokens?: number;
|
|
44
|
+
}
|
|
45
|
+
export declare function recordRoutingOutcome(home: string, row: RoutingOutcome): Promise<void>;
|
|
46
|
+
export interface RoutingStats {
|
|
47
|
+
total: number;
|
|
48
|
+
agreed: number;
|
|
49
|
+
disagreementRate: number;
|
|
50
|
+
byReason: Record<string, number>;
|
|
51
|
+
/** crude lift proxy: success rate of runs where shadow agreed vs disagreed */
|
|
52
|
+
successAgree: number | null;
|
|
53
|
+
successDisagree: number | null;
|
|
54
|
+
}
|
|
55
|
+
export declare function routingStats(home: string): Promise<RoutingStats>;
|
package/dist/router.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hmharness/kernel - shadow model router (V2 M10)
|
|
3
|
+
* Blueprint: Task Features → Router → Model → Evaluation → Routing Outcome.
|
|
4
|
+
* Phase 1 (this): pure feature extraction + a decision function that SUGGESTS
|
|
5
|
+
* a route without changing the live static routing - every run logs a
|
|
6
|
+
* routing.outcome row so the suggestion can be scored against reality before
|
|
7
|
+
* it is ever allowed to steer traffic (M9 discipline: shadow first, gate the
|
|
8
|
+
* switch). See ADR-0004.
|
|
9
|
+
*/
|
|
10
|
+
import { appendFile, mkdir, readFile } from 'node:fs/promises';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
const STEP_VERBS = /(实现|构建|scaffold|build|修复|repair|排查|diagnose|重构|refactor|迁移|migrate|部署|deploy|测试|test|发布|release)/i;
|
|
13
|
+
const MULTI_STEP = /(然后|再|接着|之后|then|after that|step \d|第[一二三1-9]步|&&)/gi;
|
|
14
|
+
const HARMONY = /(harmony|鸿蒙|arkts|cangjie|仓颉|hdc|hvigor|ohpm|hap|module\.json5|ability|ets\b)/i;
|
|
15
|
+
const CANGJIE = /(cangjie|仓颉|\.cj\b)/i;
|
|
16
|
+
const ARKTS = /(arkts|\.ets\b)/i;
|
|
17
|
+
const JSONISH = /(\.json5?|manifest|配置文件|schema)/i;
|
|
18
|
+
const SHELLY = /(命令行|shell|cmd|powershell|脚本|terminal)/i;
|
|
19
|
+
export function extractFeatures(task) {
|
|
20
|
+
const len = task.length;
|
|
21
|
+
const steps = (task.match(MULTI_STEP) ?? []).length;
|
|
22
|
+
const verb = STEP_VERBS.test(task) ? 1 : 0;
|
|
23
|
+
const complexity = Math.max(0, Math.min(1, len / 600 * 0.4 + steps / 3 * 0.3 + verb * 0.3));
|
|
24
|
+
const language = CANGJIE.test(task) ? 'cangjie'
|
|
25
|
+
: ARKTS.test(task) ? 'arkts'
|
|
26
|
+
: JSONISH.test(task) ? 'json'
|
|
27
|
+
: SHELLY.test(task) ? 'shell' : 'other';
|
|
28
|
+
const domain = HARMONY.test(task) ? 'harmony' : 'generic';
|
|
29
|
+
const expectedContext = complexity > 0.6 ? 'large' : complexity > 0.3 ? 'medium' : 'small';
|
|
30
|
+
return { complexity: Math.round(complexity * 100) / 100, language, domain, expectedContext };
|
|
31
|
+
}
|
|
32
|
+
/** Pure suggestion: harmony-domain heavy tasks want the domain-tuned route;
|
|
33
|
+
* huge-context tasks want the long-window model; everything else keeps the
|
|
34
|
+
* configured default. The caller supplies the route universe. */
|
|
35
|
+
export function routeDecision(features, opts) {
|
|
36
|
+
if (features.domain === 'harmony' && opts.harmonyRoute && opts.harmonyRoute !== opts.actual) {
|
|
37
|
+
return { actual: opts.actual, features, suggested: opts.harmonyRoute, reason: `harmony-domain task (complexity ${features.complexity})` };
|
|
38
|
+
}
|
|
39
|
+
if (features.expectedContext === 'large' && opts.heavyRoute && opts.heavyRoute !== opts.actual) {
|
|
40
|
+
return { actual: opts.actual, features, suggested: opts.heavyRoute, reason: `large expected context (${features.complexity})` };
|
|
41
|
+
}
|
|
42
|
+
return { actual: opts.actual, features, suggested: opts.actual, reason: 'default route fits' };
|
|
43
|
+
}
|
|
44
|
+
export async function recordRoutingOutcome(home, row) {
|
|
45
|
+
const dir = join(home, 'evolution');
|
|
46
|
+
await mkdir(dir, { recursive: true });
|
|
47
|
+
await appendFile(join(dir, 'routing.jsonl'), JSON.stringify(row) + '\n', 'utf8');
|
|
48
|
+
}
|
|
49
|
+
export async function routingStats(home) {
|
|
50
|
+
let text = '';
|
|
51
|
+
try {
|
|
52
|
+
text = await readFile(join(home, 'evolution', 'routing.jsonl'), 'utf8');
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return { total: 0, agreed: 0, disagreementRate: 0, byReason: {}, successAgree: null, successDisagree: null };
|
|
56
|
+
}
|
|
57
|
+
const rows = text.split('\n').filter(Boolean).map((l) => { try {
|
|
58
|
+
return JSON.parse(l);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
} }).filter(Boolean);
|
|
63
|
+
const byReason = {};
|
|
64
|
+
let agreed = 0;
|
|
65
|
+
let agreeOk = 0, agreeN = 0, disOk = 0, disN = 0;
|
|
66
|
+
for (const r of rows) {
|
|
67
|
+
const same = r.actual === r.suggested;
|
|
68
|
+
if (same)
|
|
69
|
+
agreed++;
|
|
70
|
+
byReason[r.reason] = (byReason[r.reason] ?? 0) + 1;
|
|
71
|
+
if (r.outcome) {
|
|
72
|
+
if (same) {
|
|
73
|
+
agreeN++;
|
|
74
|
+
if (r.outcome === 'ok')
|
|
75
|
+
agreeOk++;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
disN++;
|
|
79
|
+
if (r.outcome === 'ok')
|
|
80
|
+
disOk++;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
total: rows.length,
|
|
86
|
+
agreed,
|
|
87
|
+
disagreementRate: rows.length ? Math.round((rows.length - agreed) / rows.length * 100) / 100 : 0,
|
|
88
|
+
byReason,
|
|
89
|
+
successAgree: agreeN >= 8 ? agreeOk / agreeN : null,
|
|
90
|
+
successDisagree: disN >= 8 ? disOk / disN : null,
|
|
91
|
+
};
|
|
92
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hmharness/kernel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "hmharness kernel: tool registry, provider adapters, the agent loop, session log, config. Zero runtime dependencies (Node >=22 native fetch).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|