@goodandready/dsh-moa 0.2.76 → 0.2.77
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/README.md +2 -0
- package/README.ru.md +2 -0
- package/README.zh.md +2 -0
- package/lib/client.js +52 -2
- package/lib/events.js +2 -0
- package/lib/moa-schema.js +10 -0
- package/lib/pipeline/mutation-tester.js +323 -0
- package/lib/pipeline/runner.js +37 -2
- package/lib/pipeline/runtime-profiler.js +270 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,6 +54,8 @@ Single-model AI generation often suffers from blind spots, single-perspective bi
|
|
|
54
54
|
11. **Interactive Workbench (File Scoring 1–10, User Pick & Mix, Top-Model Glue & Judge Re-Check)**: Judge scores each candidate file (1–10) with pros and cons. Users can mix and match files across proposals in the WebUI. The turn's top-performing reference model harmonizes cross-file imports and contracts, followed by a final judge re-audit before promotion. Companion integration with [`dsh-time-machine`](https://github.com/goodandready/dsh-time-machine) for safe pre-promotion rollback checkpoints.
|
|
55
55
|
12. **Adversarial White-Hat Security Fuzzing Gate (#302)**: Red Team security audit scanning candidate code for SQLi, XSS, Command Injection, Path Traversal, ReDoS, and Prototype Pollution with blocking `SECURITY_VETO`.
|
|
56
56
|
13. **Chain-of-Verification (CoVe) Fact-Checking Gate for Code (#314)**: Independent pre-judge verification formulating factual API questions, validating modules and functions against standard library and project manifests to eliminate hallucinated methods.
|
|
57
|
+
14. **Runtime CPU & Memory Profiler Gate (#315)**: Sandboxed micro-benchmarking of candidate execution duration, ops/sec throughput, and heap usage with hard timeout protection to select algorithmically optimal code.
|
|
58
|
+
15. **Mutation Testing Gate for Candidate Tests (#316)**: Injects AST-level fault mutations to compute Mutation Score (killed vs survived mutants), weeding out tautological assertions and fake test suites.
|
|
57
59
|
|
|
58
60
|
---
|
|
59
61
|
|
package/README.ru.md
CHANGED
|
@@ -54,6 +54,8 @@
|
|
|
54
54
|
11. **Интерактивный воркбенч (Оценка файлов 1–10, выбор в UI, склейка лучшей моделью и повторный аудит)**: Судья оценивает каждый сгенерированный файл каждого кандидата (1–10) с указанием ключевых плюсов и минусов. Пользователь комбинирует желаемый набор файлов в WebUI; лучшая модель раунда вызывается для гармонизации импортов и контрактов, после чего судья выполняет финальную валидацию перед промоушеном. Совместимо с [`dsh-time-machine`](https://github.com/goodandready/dsh-time-machine) для безопасных теневых снимков Git.
|
|
55
55
|
12. **Состязательный White-Hat фильтр безопасности (#302)**: Red Team аудит безопасности перед арбитражем судьи, выявляющий SQLi, XSS, инъекции команд, Path Traversal, ReDoS и Prototype Pollution с блокирующим вердиктом `SECURITY_VETO`.
|
|
56
56
|
13. **Фильтр верификации фактов и API (CoVe) (#314)**: Автономная цепочка верификации (Chain-of-Verification), формулирующая проверочные вопросы по вызовам методов и пакетам, исключающая галлюцинированные и несуществующие API библиотек.
|
|
57
|
+
14. **Профайлер производительности CPU и памяти (#315)**: Изолированный бенчмаркинг времени выполнения кандидатов, пропускной способности (ops/sec) и потребления heap-памяти с защитой от зависаний для выбора алгоритмически оптимального кода.
|
|
58
|
+
15. **Мутационное тестирование тестов кандидатов (#316)**: Внедрение AST-мутаций кода для расчёта Mutation Score (убитые vs выжившие мутанты), устраняющее ложноположительные и тавтологические тесты.
|
|
57
59
|
|
|
58
60
|
---
|
|
59
61
|
|
package/README.zh.md
CHANGED
|
@@ -54,6 +54,8 @@
|
|
|
54
54
|
11. **交互式工作台 (1-10分文件打分、自由混搭、最优模型粘合与裁判复审)**:裁判对每个候选模型生成的每个文件进行 1–10 分打分并列出优缺点。用户可在 WebUI 中自由勾选组合所需文件;当轮最高分模型被调用以粘合协同跨文件导入与类型接口,随后由裁判进行最终复审。支持配套插件 [`dsh-time-machine`](https://github.com/goodandready/dsh-time-machine) 在文件提升前创建影子 Git 回滚快照。
|
|
55
55
|
12. **对抗性白帽安全模糊测试关卡 (#302)**:红队安全审计器在裁判前检查 SQLi、XSS、命令注入、路径穿越、ReDoS 和原型污染漏洞,发现严重漏洞时触发 `SECURITY_VETO` 否决。
|
|
56
56
|
13. **代码事实与 API 验证链关卡 (CoVe) (#314)**:独立的裁判前事实核验,生成针对性 API 问题并对照标准库及项目配置清单,杜绝调用虚构不存在的幻觉方法。
|
|
57
|
+
14. **运行期 CPU 与内存性能分析关卡 (#315)**:在隔离沙箱中微基准测试候选方案执行延迟、吞吐量(ops/sec)与堆内存占用,带有硬超时保护,指引裁判挑选算法最优代码。
|
|
58
|
+
15. **候选测试套件变异测试关卡 (#316)**:注入 AST 级别故障变异以计算变异得分(消灭 vs 存活变异体),剔除恒真废测试与虚假测试套件。
|
|
57
59
|
|
|
58
60
|
---
|
|
59
61
|
|
package/lib/client.js
CHANGED
|
@@ -232,7 +232,11 @@ window.__ModuleLoader__.load({
|
|
|
232
232
|
"aggregator.security_gate_title": "\u{1F6E1}\uFE0F Adversarial White-Hat Security Fuzzing Gate",
|
|
233
233
|
"aggregator.security_gate_desc": "Red-team security auditor checks candidate proposals for SQLi, XSS, ReDoS, Command Injection and Path Traversal with blocking SECURITY_VETO.",
|
|
234
234
|
"aggregator.cove_gate_title": "\u{1F50D} Chain-of-Verification (CoVe) Fact-Checking Gate",
|
|
235
|
-
"aggregator.cove_gate_desc": "Formulates API existence & method signature verification questions, disqualifying candidates that use hallucinated methods."
|
|
235
|
+
"aggregator.cove_gate_desc": "Formulates API existence & method signature verification questions, disqualifying candidates that use hallucinated methods.",
|
|
236
|
+
"aggregator.profiler_gate_title": "\u26A1 Automated Runtime Memory & CPU Profiler Gate",
|
|
237
|
+
"aggregator.profiler_gate_desc": "Runs isolated micro-benchmarks measuring execution latency, throughput (ops/sec), and heap memory usage.",
|
|
238
|
+
"aggregator.mutation_gate_title": "\u{1F9EC} Runtime Mutation Testing Gate for Candidate Tests",
|
|
239
|
+
"aggregator.mutation_gate_desc": "Injects synthetic faults into candidate code to test test-suite robustness, eliminating tautological and weak tests."
|
|
236
240
|
};
|
|
237
241
|
const zh = {
|
|
238
242
|
title: "\u667A\u80FD\u4F53\u6DF7\u5408 (MoA)",
|
|
@@ -455,7 +459,11 @@ window.__ModuleLoader__.load({
|
|
|
455
459
|
"aggregator.security_gate_title": "\u{1F6E1}\uFE0F \u5BF9\u6297\u6027\u767D\u5E3D\u5B89\u5168\u6A21\u7CCA\u6D4B\u8BD5\u5173\u5361",
|
|
456
460
|
"aggregator.security_gate_desc": "\u7EA2\u961F\u5B89\u5168\u5BA1\u8BA1\u5668\u68C0\u67E5\u5019\u9009\u65B9\u6848\u4E2D\u7684 SQLi\u3001XSS\u3001ReDoS\u3001\u547D\u4EE4\u6CE8\u5165\u548C\u8DEF\u5F84\u7A7F\u8D8A\u6F0F\u6D1E\uFF0C\u5E76\u5728\u53D1\u73B0\u4E25\u91CD\u6F0F\u6D1E\u65F6\u89E6\u53D1 SECURITY_VETO \u5426\u51B3\u3002",
|
|
457
461
|
"aggregator.cove_gate_title": "\u{1F50D} \u9A8C\u8BC1\u94FE (CoVe) \u4EE3\u7801\u4E8B\u5B9E\u4E0E API \u6838\u67E5\u5173\u5361",
|
|
458
|
-
"aggregator.cove_gate_desc": "\u751F\u6210 API \u5B58\u5728\u6027\u4E0E\u65B9\u6CD5\u7B7E\u540D\u6838\u9A8C\u95EE\u9898\uFF0C\u81EA\u52A8\u60E9\u7F5A\u6216\u53D6\u6D88\u4F7F\u7528\u865A\u6784\u5E7B\u89C9\u65B9\u6CD5\u7684\u5019\u9009\u65B9\u6848\u3002"
|
|
462
|
+
"aggregator.cove_gate_desc": "\u751F\u6210 API \u5B58\u5728\u6027\u4E0E\u65B9\u6CD5\u7B7E\u540D\u6838\u9A8C\u95EE\u9898\uFF0C\u81EA\u52A8\u60E9\u7F5A\u6216\u53D6\u6D88\u4F7F\u7528\u865A\u6784\u5E7B\u89C9\u65B9\u6CD5\u7684\u5019\u9009\u65B9\u6848\u3002",
|
|
463
|
+
"aggregator.profiler_gate_title": "\u26A1 \u81EA\u52A8\u8FD0\u884C\u65F6 CPU \u4E0E\u5185\u5B58\u6027\u80FD\u5206\u6790\u5173\u5361",
|
|
464
|
+
"aggregator.profiler_gate_desc": "\u5728\u9694\u79BB\u6C99\u7BB1\u4E2D\u8FD0\u884C\u5FAE\u57FA\u51C6\u6D4B\u8BD5\uFF0C\u7CBE\u786E\u5EA6\u91CF\u6267\u884C\u5EF6\u8FDF\u3001\u541E\u5410\u91CF (ops/sec) \u548C\u5806\u5185\u5B58\u5206\u914D\u3002",
|
|
465
|
+
"aggregator.mutation_gate_title": "\u{1F9EC} \u5019\u9009\u6D4B\u8BD5\u5957\u4EF6\u8FD0\u884C\u65F6\u53D8\u5F02\u6D4B\u8BD5\u5173\u5361",
|
|
466
|
+
"aggregator.mutation_gate_desc": "\u5411\u4EE3\u7801\u6CE8\u5165\u5408\u6210\u6545\u969C\u5E76\u9A8C\u8BC1\u6D4B\u8BD5\u7684\u6355\u83B7\u80FD\u529B\uFF0C\u5254\u9664\u540C\u4E49\u53CD\u590D\u4E0E\u65E0\u6548\u7684\u865A\u5047\u6D4B\u8BD5\u3002"
|
|
459
467
|
};
|
|
460
468
|
function makeT(dict, fallback) {
|
|
461
469
|
return function t(key, vars) {
|
|
@@ -2660,6 +2668,48 @@ window.__ModuleLoader__.load({
|
|
|
2660
2668
|
t("aggregator.cove_gate_desc") || "Formulates API existence & method signature verification questions, disqualifying candidates that use hallucinated methods."
|
|
2661
2669
|
)
|
|
2662
2670
|
),
|
|
2671
|
+
/* Automated Runtime Memory & CPU Profiler Gate (#315) */
|
|
2672
|
+
React.createElement(
|
|
2673
|
+
"div",
|
|
2674
|
+
{ style: { display: "flex", flexDirection: "column", gap: 6, padding: "8px 12px", background: "var(--dsw-alias-bg-layer-2)", borderRadius: 8, border: "1px solid var(--dsw-alias-border-l2)", marginTop: 8 } },
|
|
2675
|
+
React.createElement(
|
|
2676
|
+
"label",
|
|
2677
|
+
{ style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 13, fontWeight: 500 } },
|
|
2678
|
+
React.createElement("input", {
|
|
2679
|
+
type: "checkbox",
|
|
2680
|
+
checked: Boolean(currentPreset.profiler_gate_enabled),
|
|
2681
|
+
onChange: (e) => updateCurrentPreset((p) => ({ ...p, profiler_gate_enabled: e.target.checked })),
|
|
2682
|
+
style: { accentColor: "var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))", cursor: "pointer" }
|
|
2683
|
+
}),
|
|
2684
|
+
t("aggregator.profiler_gate_title") || "\u26A1 Automated Runtime Memory & CPU Profiler Gate"
|
|
2685
|
+
),
|
|
2686
|
+
React.createElement(
|
|
2687
|
+
"div",
|
|
2688
|
+
{ style: { fontSize: 12, color: "var(--dsw-alias-label-secondary)", marginLeft: 22 } },
|
|
2689
|
+
t("aggregator.profiler_gate_desc") || "Runs isolated micro-benchmarks measuring execution latency, throughput (ops/sec), and heap memory usage."
|
|
2690
|
+
)
|
|
2691
|
+
),
|
|
2692
|
+
/* Runtime Mutation Testing Gate (#316) */
|
|
2693
|
+
React.createElement(
|
|
2694
|
+
"div",
|
|
2695
|
+
{ style: { display: "flex", flexDirection: "column", gap: 6, padding: "8px 12px", background: "var(--dsw-alias-bg-layer-2)", borderRadius: 8, border: "1px solid var(--dsw-alias-border-l2)", marginTop: 8 } },
|
|
2696
|
+
React.createElement(
|
|
2697
|
+
"label",
|
|
2698
|
+
{ style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 13, fontWeight: 500 } },
|
|
2699
|
+
React.createElement("input", {
|
|
2700
|
+
type: "checkbox",
|
|
2701
|
+
checked: Boolean(currentPreset.mutation_testing_enabled),
|
|
2702
|
+
onChange: (e) => updateCurrentPreset((p) => ({ ...p, mutation_testing_enabled: e.target.checked })),
|
|
2703
|
+
style: { accentColor: "var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))", cursor: "pointer" }
|
|
2704
|
+
}),
|
|
2705
|
+
t("aggregator.mutation_gate_title") || "\u{1F9EC} Runtime Mutation Testing Gate for Candidate Tests"
|
|
2706
|
+
),
|
|
2707
|
+
React.createElement(
|
|
2708
|
+
"div",
|
|
2709
|
+
{ style: { fontSize: 12, color: "var(--dsw-alias-label-secondary)", marginLeft: 22 } },
|
|
2710
|
+
t("aggregator.mutation_gate_desc") || "Injects synthetic faults into candidate code to test test-suite robustness, eliminating tautological and weak tests."
|
|
2711
|
+
)
|
|
2712
|
+
),
|
|
2663
2713
|
/* Fallback Judges Chain */
|
|
2664
2714
|
React.createElement(
|
|
2665
2715
|
"div",
|
package/lib/events.js
CHANGED
|
@@ -13,6 +13,8 @@ export const MOA_EVENTS = {
|
|
|
13
13
|
PREFERENCES_APPLIED: 'moa/preferences-applied',
|
|
14
14
|
SECURITY_AUDIT: 'moa/security-audit',
|
|
15
15
|
COVE_VERIFIED: 'moa/cove-verified',
|
|
16
|
+
PROFILER_COMPLETE: 'moa/profiler-complete',
|
|
17
|
+
MUTATION_COMPLETE: 'moa/mutation-complete',
|
|
16
18
|
CRITIQUE_COMPLETE: 'moa/critique-complete',
|
|
17
19
|
JUDGE_VERDICT: 'moa/judge-verdict',
|
|
18
20
|
CONTEXT_PRUNING: 'moa/context-pruning',
|
package/lib/moa-schema.js
CHANGED
|
@@ -95,6 +95,11 @@ export const PresetSchema = z.object({
|
|
|
95
95
|
security_gate_strict_veto: z.boolean().default(true),
|
|
96
96
|
cove_verification_enabled: z.boolean().default(false),
|
|
97
97
|
cove_max_questions: z.number().default(5),
|
|
98
|
+
profiler_gate_enabled: z.boolean().default(false),
|
|
99
|
+
profiler_iterations: z.number().default(100),
|
|
100
|
+
profiler_timeout_ms: z.number().default(2000),
|
|
101
|
+
mutation_testing_enabled: z.boolean().default(false),
|
|
102
|
+
min_mutation_score: z.number().default(0.70),
|
|
98
103
|
})
|
|
99
104
|
|
|
100
105
|
export const Config = z.object({
|
|
@@ -136,6 +141,11 @@ export const Config = z.object({
|
|
|
136
141
|
security_gate_strict_veto: z.boolean().default(true).volatile(),
|
|
137
142
|
cove_verification_enabled: z.boolean().default(false).volatile(),
|
|
138
143
|
cove_max_questions: z.number().default(5).volatile(),
|
|
144
|
+
profiler_gate_enabled: z.boolean().default(false).volatile(),
|
|
145
|
+
profiler_iterations: z.number().default(100).volatile(),
|
|
146
|
+
profiler_timeout_ms: z.number().default(2000).volatile(),
|
|
147
|
+
mutation_testing_enabled: z.boolean().default(false).volatile(),
|
|
148
|
+
min_mutation_score: z.number().default(0.70).volatile(),
|
|
139
149
|
smart_routing_model: ModelSlotSchema.default({ provider: '', model: '' }).volatile(),
|
|
140
150
|
prices: z.dict(PriceRow).default({}).volatile(),
|
|
141
151
|
presets: z.array(PresetSchema).default([
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek Harness Mixture of Agents (MoA) — Mutation Testing Gate for Candidate Tests (#316)
|
|
3
|
+
*
|
|
4
|
+
* Injects deterministic AST-level fault mutations into candidate implementations and evaluates
|
|
5
|
+
* candidate test suites against mutants to calculate Mutation Score (killed vs survived mutants),
|
|
6
|
+
* eliminating false-positive and tautological tests before Judge arbitration.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import vm from 'node:vm'
|
|
10
|
+
import { MOA_EVENTS, emitEvent } from '../events.js'
|
|
11
|
+
import { extractExecutableCode } from './runtime-profiler.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Mutation operator patterns.
|
|
15
|
+
*/
|
|
16
|
+
export const MUTATION_OPERATORS = [
|
|
17
|
+
{
|
|
18
|
+
name: 'invert_strict_equality',
|
|
19
|
+
regex: /===/g,
|
|
20
|
+
replacement: '!==',
|
|
21
|
+
description: 'Replaced strict equality (===) with inequality (!==)',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'invert_relational_lt',
|
|
25
|
+
regex: /<(?!=)/g,
|
|
26
|
+
replacement: '>=',
|
|
27
|
+
description: 'Replaced less-than (<) with greater-or-equal (>=)',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'invert_relational_gt',
|
|
31
|
+
regex: />(?!=)/g,
|
|
32
|
+
replacement: '<=',
|
|
33
|
+
description: 'Replaced greater-than (>) with less-or-equal (<=)',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'invert_logical_and',
|
|
37
|
+
regex: /&&/g,
|
|
38
|
+
replacement: '||',
|
|
39
|
+
description: 'Replaced logical AND (&&) with logical OR (||)',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'invert_arithmetic_plus',
|
|
43
|
+
regex: /(?<=\w\s*)\+(?=\s*\w)/g,
|
|
44
|
+
replacement: '-',
|
|
45
|
+
description: 'Replaced addition (+) with subtraction (-)',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'invert_boolean_true',
|
|
49
|
+
regex: /\btrue\b/g,
|
|
50
|
+
replacement: 'false',
|
|
51
|
+
description: 'Replaced literal true with false',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'mutate_return_zero',
|
|
55
|
+
regex: /return\s+0;/g,
|
|
56
|
+
replacement: 'return 1;',
|
|
57
|
+
description: 'Mutated return 0 to return 1',
|
|
58
|
+
},
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Generates an array of mutant variations from a single source code string.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} sourceCode
|
|
65
|
+
* @param {object} [options]
|
|
66
|
+
* @returns {Array<object>} Up to maxMutants mutant objects
|
|
67
|
+
*/
|
|
68
|
+
export function generateMutants(sourceCode, options = {}) {
|
|
69
|
+
const maxMutants = Math.min(10, Math.max(1, options.maxMutants || 5))
|
|
70
|
+
const mutants = []
|
|
71
|
+
if (!sourceCode || typeof sourceCode !== 'string') return mutants
|
|
72
|
+
|
|
73
|
+
for (const op of MUTATION_OPERATORS) {
|
|
74
|
+
let match
|
|
75
|
+
const re = new RegExp(op.regex.source, op.regex.flags)
|
|
76
|
+
while ((match = re.exec(sourceCode)) !== null) {
|
|
77
|
+
const idx = match.index
|
|
78
|
+
const len = match[0].length
|
|
79
|
+
const mutantCode = sourceCode.slice(0, idx) + op.replacement + sourceCode.slice(idx + len)
|
|
80
|
+
|
|
81
|
+
mutants.push({
|
|
82
|
+
id: mutants.length + 1,
|
|
83
|
+
operator: op.name,
|
|
84
|
+
description: op.description,
|
|
85
|
+
originalSnippet: match[0],
|
|
86
|
+
replacementSnippet: op.replacement,
|
|
87
|
+
mutantCode,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
if (mutants.length >= maxMutants) return mutants
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return mutants
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Runs a test script against an implementation in an isolated VM context.
|
|
99
|
+
*
|
|
100
|
+
* @param {string} codeToExecute
|
|
101
|
+
* @param {number} [timeoutMs=1000]
|
|
102
|
+
* @returns {{ ok: boolean, error: string | null }}
|
|
103
|
+
*/
|
|
104
|
+
function executeScript(codeToExecute, timeoutMs = 1000) {
|
|
105
|
+
const sandbox = {
|
|
106
|
+
console: { log: () => {}, warn: () => {}, error: () => {} },
|
|
107
|
+
assert: (cond, msg) => { if (!cond) throw new Error(msg || 'Assertion failed'); },
|
|
108
|
+
Math, Date, Array, Object, String, Number, Boolean, Set, Map, JSON,
|
|
109
|
+
}
|
|
110
|
+
sandbox.assert.equal = (a, b) => { if (a !== b) throw new Error(`Assertion failed: ${a} !== ${b}`); }
|
|
111
|
+
sandbox.assert.strictEqual = (a, b) => { if (a !== b) throw new Error(`Assertion failed: ${a} !== ${b}`); }
|
|
112
|
+
sandbox.assert.ok = (v) => { if (!v) throw new Error('Assertion failed: falsy value'); }
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const context = vm.createContext(sandbox)
|
|
116
|
+
const script = new vm.Script(codeToExecute)
|
|
117
|
+
script.runInContext(context, { timeout: timeoutMs })
|
|
118
|
+
return { ok: true, error: null }
|
|
119
|
+
} catch (err) {
|
|
120
|
+
return { ok: false, error: err.message }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Evaluates a candidate's implementation against generated mutants.
|
|
126
|
+
*
|
|
127
|
+
* @param {string} rawText - Candidate text containing code or tests
|
|
128
|
+
* @param {object} [options]
|
|
129
|
+
* @returns {object} Mutation testing report
|
|
130
|
+
*/
|
|
131
|
+
export function auditCandidateMutations(rawText, options = {}) {
|
|
132
|
+
const { minMutationScore = 0.7, maxMutants = 5, timeoutMs = 1000 } = options
|
|
133
|
+
const code = extractExecutableCode(rawText)
|
|
134
|
+
|
|
135
|
+
if (!code || !code.trim()) {
|
|
136
|
+
return {
|
|
137
|
+
passed: true,
|
|
138
|
+
mutationScore: 100,
|
|
139
|
+
totalMutants: 0,
|
|
140
|
+
killedCount: 0,
|
|
141
|
+
survivedCount: 0,
|
|
142
|
+
mutants: [],
|
|
143
|
+
summary: 'Mutation Testing: No code to mutate.',
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 1. Verify baseline executes cleanly
|
|
148
|
+
const baseline = executeScript(code, timeoutMs)
|
|
149
|
+
if (!baseline.ok) {
|
|
150
|
+
return {
|
|
151
|
+
passed: false,
|
|
152
|
+
mutationScore: 0,
|
|
153
|
+
totalMutants: 0,
|
|
154
|
+
killedCount: 0,
|
|
155
|
+
survivedCount: 0,
|
|
156
|
+
mutants: [],
|
|
157
|
+
error: `Baseline test execution failed: ${baseline.error}`,
|
|
158
|
+
summary: `Baseline execution failed (${baseline.error}).`,
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 2. Generate mutations on the code
|
|
163
|
+
const mutants = generateMutants(code, { maxMutants })
|
|
164
|
+
if (mutants.length === 0) {
|
|
165
|
+
return {
|
|
166
|
+
passed: true,
|
|
167
|
+
mutationScore: 100,
|
|
168
|
+
totalMutants: 0,
|
|
169
|
+
killedCount: 0,
|
|
170
|
+
survivedCount: 0,
|
|
171
|
+
mutants: [],
|
|
172
|
+
summary: 'Mutation Testing: Code contains no mutable operators.',
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 3. Test each mutant
|
|
177
|
+
let killedCount = 0
|
|
178
|
+
let survivedCount = 0
|
|
179
|
+
const evaluatedMutants = []
|
|
180
|
+
|
|
181
|
+
for (const m of mutants) {
|
|
182
|
+
const res = executeScript(m.mutantCode, timeoutMs)
|
|
183
|
+
if (!res.ok) {
|
|
184
|
+
// Mutant was killed (good! The test broke as expected)
|
|
185
|
+
killedCount++
|
|
186
|
+
evaluatedMutants.push({
|
|
187
|
+
...m,
|
|
188
|
+
status: 'KILLED',
|
|
189
|
+
reason: res.error,
|
|
190
|
+
})
|
|
191
|
+
} else {
|
|
192
|
+
// Mutant survived (bad! Code was broken but test passed anyway)
|
|
193
|
+
survivedCount++
|
|
194
|
+
evaluatedMutants.push({
|
|
195
|
+
...m,
|
|
196
|
+
status: 'SURVIVED',
|
|
197
|
+
reason: 'Test suite passed despite injected fault.',
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const mutationRatio = Number((killedCount / mutants.length).toFixed(2))
|
|
203
|
+
const mutationScore = Math.round(mutationRatio * 100)
|
|
204
|
+
const passed = mutationRatio >= minMutationScore
|
|
205
|
+
|
|
206
|
+
const summary = passed
|
|
207
|
+
? `🧬 Mutation Testing: PASSED (Score ${mutationScore}%). Killed ${killedCount}/${mutants.length} mutant(s).`
|
|
208
|
+
: `⚠️ Mutation Testing: WEAK TESTS (Score ${mutationScore}% < ${Math.round(minMutationScore * 100)}%). ${survivedCount} mutant(s) survived undetected.`
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
passed,
|
|
212
|
+
mutationScore,
|
|
213
|
+
mutationRatio,
|
|
214
|
+
totalMutants: mutants.length,
|
|
215
|
+
killedCount,
|
|
216
|
+
survivedCount,
|
|
217
|
+
mutants: evaluatedMutants,
|
|
218
|
+
summary,
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Runs the Mutation Testing Gate across all candidate solutions.
|
|
224
|
+
*
|
|
225
|
+
* @param {Array<object>} candidates
|
|
226
|
+
* @param {object} [options]
|
|
227
|
+
* @returns {object} Mutation gate report
|
|
228
|
+
*/
|
|
229
|
+
export function runMutationGate(candidates, options = {}) {
|
|
230
|
+
const { ctx, runId, onProgress, minMutationScore = 0.70 } = options
|
|
231
|
+
const results = []
|
|
232
|
+
|
|
233
|
+
if (!Array.isArray(candidates) || candidates.length === 0) {
|
|
234
|
+
return {
|
|
235
|
+
results: [],
|
|
236
|
+
passedCount: 0,
|
|
237
|
+
mutationDirective: null,
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
242
|
+
const cand = candidates[i]
|
|
243
|
+
const text = typeof cand === 'string' ? cand : cand.output || cand.text || cand.code || ''
|
|
244
|
+
const report = auditCandidateMutations(text, { minMutationScore, ...options })
|
|
245
|
+
|
|
246
|
+
if (typeof cand === 'object' && cand !== null) {
|
|
247
|
+
cand.mutationReport = report
|
|
248
|
+
cand.mutationScore = report.mutationScore
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
results.push({
|
|
252
|
+
candidateIndex: i,
|
|
253
|
+
model: cand?.model || `Candidate ${i + 1}`,
|
|
254
|
+
report,
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
if (ctx) {
|
|
258
|
+
emitEvent(ctx, MOA_EVENTS.MUTATION_COMPLETE, {
|
|
259
|
+
runId,
|
|
260
|
+
candidateIndex: i,
|
|
261
|
+
mutationScore: report.mutationScore,
|
|
262
|
+
killedCount: report.killedCount,
|
|
263
|
+
totalMutants: report.totalMutants,
|
|
264
|
+
passed: report.passed,
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const passedCount = results.filter((r) => r.report.passed).length
|
|
270
|
+
|
|
271
|
+
if (typeof onProgress === 'function' && results.length > 0) {
|
|
272
|
+
const summaryList = results.map((r) => `Ref ${r.candidateIndex + 1}: ${r.report.mutationScore}%`).join(', ')
|
|
273
|
+
onProgress(`🧬 *Mutation Testing Gate*: ${summaryList}\n`)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const mutationDirective = formatMutationDirective(results)
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
results,
|
|
280
|
+
passedCount,
|
|
281
|
+
mutationDirective,
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Formats mutation testing results into a prompt directive for the Judge.
|
|
287
|
+
*
|
|
288
|
+
* @param {Array<object>} results
|
|
289
|
+
* @returns {string|null}
|
|
290
|
+
*/
|
|
291
|
+
export function formatMutationDirective(results) {
|
|
292
|
+
if (!Array.isArray(results) || results.length === 0) return null
|
|
293
|
+
|
|
294
|
+
const lines = [
|
|
295
|
+
'### 🧬 RUNTIME MUTATION TESTING & FAULT INJECTION REPORT',
|
|
296
|
+
'Evaluation of test robustness against synthetic fault injection (killing code mutants):',
|
|
297
|
+
]
|
|
298
|
+
|
|
299
|
+
for (const r of results) {
|
|
300
|
+
const label = `Candidate ${r.candidateIndex + 1}`
|
|
301
|
+
const rep = r.report
|
|
302
|
+
if (rep.totalMutants === 0) {
|
|
303
|
+
lines.push(`- **${label}**: ℹ️ No mutable operators found in test suite.`)
|
|
304
|
+
} else if (rep.passed) {
|
|
305
|
+
lines.push(
|
|
306
|
+
`- **${label}**: ✅ **RESILIENT TESTS** (Score ${rep.mutationScore}%) — Killed ${rep.killedCount}/${rep.totalMutants} fault mutants.`
|
|
307
|
+
)
|
|
308
|
+
} else {
|
|
309
|
+
lines.push(
|
|
310
|
+
`- **${label}**: ⚠️ **WEAK/TAUTOLOGICAL TESTS** (Score ${rep.mutationScore}%) — ${rep.survivedCount}/${rep.totalMutants} mutant(s) survived! The tests pass even when logic is inverted.`
|
|
311
|
+
)
|
|
312
|
+
for (const m of (rep.mutants || []).filter((m) => m.status === 'SURVIVED')) {
|
|
313
|
+
lines.push(` * ${m.description} [Survived]`)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
lines.push(
|
|
319
|
+
'\n**Judge Quality Mandate**: Prefer solutions whose test suites have a high Mutation Score. Solutions with surviving mutants have weak or tautological tests that do not genuinely verify behavior.'
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
return lines.join('\n')
|
|
323
|
+
}
|
package/lib/pipeline/runner.js
CHANGED
|
@@ -20,6 +20,8 @@ import { applySyntaxFilter, formatSyntaxDirective } from './syntax-filter.js'
|
|
|
20
20
|
import { stageEarlyConsensus } from './early-consensus.js'
|
|
21
21
|
import { runSecurityAuditGate } from './security-fuzzer.js'
|
|
22
22
|
import { runCoveVerificationGate } from './cove-verifier.js'
|
|
23
|
+
import { runProfilerGate } from './runtime-profiler.js'
|
|
24
|
+
import { runMutationGate } from './mutation-tester.js'
|
|
23
25
|
import { emitEvent, MOA_EVENTS } from '../events.js'
|
|
24
26
|
|
|
25
27
|
export async function runMoAPipeline({
|
|
@@ -234,6 +236,39 @@ export async function runMoAPipeline({
|
|
|
234
236
|
securityDirective = securityGateResult.securityDirective
|
|
235
237
|
}
|
|
236
238
|
|
|
239
|
+
// Pre-Stage 5.10: Runtime CPU & Memory Profiler Gate (#315)
|
|
240
|
+
let profilerDirective = null
|
|
241
|
+
let profilerGateResult = null
|
|
242
|
+
if (preset?.profiler_gate_enabled) {
|
|
243
|
+
profilerGateResult = runProfilerGate(
|
|
244
|
+
syntaxResult.validCandidates.length > 0 ? syntaxResult.validCandidates : successfulRefs,
|
|
245
|
+
{
|
|
246
|
+
ctx,
|
|
247
|
+
runId,
|
|
248
|
+
onProgress,
|
|
249
|
+
iterations: preset.profiler_iterations || 100,
|
|
250
|
+
timeoutMs: preset.profiler_timeout_ms || 2000,
|
|
251
|
+
}
|
|
252
|
+
)
|
|
253
|
+
profilerDirective = profilerGateResult.profilerDirective
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Pre-Stage 5.11: Mutation Testing & Fault Injection Gate (#316)
|
|
257
|
+
let mutationDirective = null
|
|
258
|
+
let mutationGateResult = null
|
|
259
|
+
if (preset?.mutation_testing_enabled) {
|
|
260
|
+
mutationGateResult = runMutationGate(
|
|
261
|
+
syntaxResult.validCandidates.length > 0 ? syntaxResult.validCandidates : successfulRefs,
|
|
262
|
+
{
|
|
263
|
+
ctx,
|
|
264
|
+
runId,
|
|
265
|
+
onProgress,
|
|
266
|
+
minMutationScore: preset.min_mutation_score ?? 0.70,
|
|
267
|
+
}
|
|
268
|
+
)
|
|
269
|
+
mutationDirective = mutationGateResult.mutationDirective
|
|
270
|
+
}
|
|
271
|
+
|
|
237
272
|
// Pre-Stage 6: Dynamic Consensus Threshold & Multi-Judge Escalation (#300)
|
|
238
273
|
const escalationResult = evaluateJudgeEscalation({
|
|
239
274
|
candidateOutputs: syntaxResult.validCandidates.length > 0 ? syntaxResult.validCandidates : successfulRefs,
|
|
@@ -284,7 +319,7 @@ export async function runMoAPipeline({
|
|
|
284
319
|
preset: effectivePreset,
|
|
285
320
|
successfulRefs: syntaxResult.validCandidates.length > 0 ? syntaxResult.validCandidates : successfulRefs,
|
|
286
321
|
referenceOutputs, primaryJudge: effectivePrimaryJudge,
|
|
287
|
-
userPrompt, judgeCriteria: [ctxStage.judgeCriteria, coveDirective, securityDirective].filter(Boolean).join('\n\n'), prices, maxTokens: ctxStage.maxTokens,
|
|
322
|
+
userPrompt, judgeCriteria: [ctxStage.judgeCriteria, coveDirective, securityDirective, profilerDirective, mutationDirective].filter(Boolean).join('\n\n'), prices, maxTokens: ctxStage.maxTokens,
|
|
288
323
|
callLlm, signal, onProgress, cwd, runId, isRefinement: ctxStage.isRefinement,
|
|
289
324
|
isFastMode: ctxStage.isFastMode, collectedCtx: ctxStage.collectedCtx, startTime,
|
|
290
325
|
})
|
|
@@ -342,7 +377,7 @@ export async function runMoAPipeline({
|
|
|
342
377
|
}
|
|
343
378
|
}
|
|
344
379
|
|
|
345
|
-
const fileScoringDirective = [rawFileDirective, visualResult?.directive, adversarialTddResult?.directive, syntaxDirective, coveDirective, securityDirective, pruningDirective, ctxStage.stackAwareDirective, ctxStage.developerPreferencesDirective].filter(Boolean).join("\n\n") || null
|
|
380
|
+
const fileScoringDirective = [rawFileDirective, visualResult?.directive, adversarialTddResult?.directive, syntaxDirective, coveDirective, securityDirective, profilerDirective, mutationDirective, pruningDirective, ctxStage.stackAwareDirective, ctxStage.developerPreferencesDirective].filter(Boolean).join("\n\n") || null
|
|
346
381
|
|
|
347
382
|
// Stage 7: Synthesis Phase
|
|
348
383
|
const synthRes = await stageSynthesis({
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek Harness Mixture of Agents (MoA) — Runtime CPU & Memory Profiler Gate (#315)
|
|
3
|
+
*
|
|
4
|
+
* Micro-benchmarks candidate code execution duration, ops/sec throughput, and heap memory usage
|
|
5
|
+
* in an isolated sandbox with hard timeout protection to guide Judge selection towards
|
|
6
|
+
* algorithmically optimal solutions.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import vm from 'node:vm'
|
|
10
|
+
import { MOA_EVENTS, emitEvent } from '../events.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Extracts executable JavaScript code from markdown fences or raw code.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} text
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
export function extractExecutableCode(text) {
|
|
19
|
+
if (!text || typeof text !== 'string') return ''
|
|
20
|
+
const jsFenceMatch = text.match(/```(?:javascript|js|node)\s*\n([\s\S]*?)\n```/i)
|
|
21
|
+
if (jsFenceMatch && jsFenceMatch[1]) {
|
|
22
|
+
return jsFenceMatch[1]
|
|
23
|
+
}
|
|
24
|
+
const anyFenceMatch = text.match(/```\w*\s*\n([\s\S]*?)\n```/)
|
|
25
|
+
if (anyFenceMatch && anyFenceMatch[1]) {
|
|
26
|
+
return anyFenceMatch[1]
|
|
27
|
+
}
|
|
28
|
+
return text
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Profiles code execution in an isolated VM sandbox.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} rawCode - Candidate source code or markdown
|
|
35
|
+
* @param {object} [options]
|
|
36
|
+
* @returns {object} Profiler report
|
|
37
|
+
*/
|
|
38
|
+
export function profileCodeExecution(rawCode, options = {}) {
|
|
39
|
+
const {
|
|
40
|
+
iterations = 100,
|
|
41
|
+
timeoutMs = 2000,
|
|
42
|
+
} = options
|
|
43
|
+
|
|
44
|
+
const code = extractExecutableCode(rawCode)
|
|
45
|
+
if (!code || !code.trim()) {
|
|
46
|
+
return {
|
|
47
|
+
passed: false,
|
|
48
|
+
durationMs: 0,
|
|
49
|
+
heapBytesUsed: 0,
|
|
50
|
+
opsPerSec: 0,
|
|
51
|
+
iterations: 0,
|
|
52
|
+
error: 'Empty or unexecutable code provided for profiling.',
|
|
53
|
+
summary: 'Profiler skipped: no executable code.',
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Initial sandbox built-ins to ignore when scanning for user functions
|
|
58
|
+
const initialKeys = new Set([
|
|
59
|
+
'console', 'Math', 'Date', 'Array', 'Object', 'String', 'Number',
|
|
60
|
+
'Boolean', 'Set', 'Map', 'JSON', 'parseInt', 'parseFloat',
|
|
61
|
+
'globalThis', 'global', '__mainFn', '__benchFn', '__benchArg'
|
|
62
|
+
])
|
|
63
|
+
|
|
64
|
+
const sandbox = {
|
|
65
|
+
console: { log: () => {}, warn: () => {}, error: () => {} },
|
|
66
|
+
Math,
|
|
67
|
+
Date,
|
|
68
|
+
Array,
|
|
69
|
+
Object,
|
|
70
|
+
String,
|
|
71
|
+
Number,
|
|
72
|
+
Boolean,
|
|
73
|
+
Set,
|
|
74
|
+
Map,
|
|
75
|
+
JSON,
|
|
76
|
+
parseInt,
|
|
77
|
+
parseFloat,
|
|
78
|
+
}
|
|
79
|
+
sandbox.globalThis = sandbox
|
|
80
|
+
sandbox.global = sandbox
|
|
81
|
+
|
|
82
|
+
// Convert export declarations to var / global assignments
|
|
83
|
+
const cleanCode = code
|
|
84
|
+
.replace(/^\s*export\s+default\s+/gm, 'globalThis.__mainFn = ')
|
|
85
|
+
.replace(/^\s*export\s+(?:async\s+)?function\s+([a-zA-Z0-9_]+)/gm, 'function $1')
|
|
86
|
+
.replace(/^\s*export\s+const\s+([a-zA-Z0-9_]+)/gm, 'var $1')
|
|
87
|
+
.replace(/^\s*import\s+.*?;?\s*$/gm, '// [import stripped for benchmark]')
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const context = vm.createContext(sandbox)
|
|
91
|
+
// Run candidate code once to evaluate top-level declarations
|
|
92
|
+
const setupScript = new vm.Script(cleanCode)
|
|
93
|
+
setupScript.runInContext(context, { timeout: Math.min(1000, timeoutMs) })
|
|
94
|
+
|
|
95
|
+
// Find entrypoint function: either __mainFn or any user-defined function on sandbox
|
|
96
|
+
let targetFn = sandbox.__mainFn
|
|
97
|
+
if (typeof targetFn !== 'function') {
|
|
98
|
+
const userKeys = Object.keys(sandbox).filter((k) => !initialKeys.has(k) && typeof sandbox[k] === 'function')
|
|
99
|
+
if (userKeys.length > 0) {
|
|
100
|
+
targetFn = sandbox[userKeys[userKeys.length - 1]]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
sandbox.__benchFn = targetFn || null
|
|
105
|
+
sandbox.__benchArg = 10
|
|
106
|
+
|
|
107
|
+
let benchScript
|
|
108
|
+
if (typeof sandbox.__benchFn === 'function') {
|
|
109
|
+
benchScript = new vm.Script(`
|
|
110
|
+
try {
|
|
111
|
+
__benchFn();
|
|
112
|
+
} catch (_) {
|
|
113
|
+
try { __benchFn(__benchArg); } catch (__) {}
|
|
114
|
+
}
|
|
115
|
+
`)
|
|
116
|
+
} else {
|
|
117
|
+
benchScript = new vm.Script(`(() => { ${cleanCode} })()`)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Warm-up run
|
|
121
|
+
benchScript.runInContext(context, { timeout: Math.min(1000, timeoutMs) })
|
|
122
|
+
|
|
123
|
+
// Measure memory and time
|
|
124
|
+
const startMem = process.memoryUsage().heapUsed
|
|
125
|
+
const startHr = process.hrtime.bigint()
|
|
126
|
+
|
|
127
|
+
for (let i = 0; i < iterations; i++) {
|
|
128
|
+
benchScript.runInContext(context, { timeout: Math.min(500, timeoutMs) })
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const endHr = process.hrtime.bigint()
|
|
132
|
+
const endMem = process.memoryUsage().heapUsed
|
|
133
|
+
|
|
134
|
+
const totalNs = Number(endHr - startHr)
|
|
135
|
+
const durationMs = Number((totalNs / 1_000_000).toFixed(3))
|
|
136
|
+
const avgLatencyMs = Number((durationMs / iterations).toFixed(4))
|
|
137
|
+
const heapBytesUsed = Math.max(0, endMem - startMem)
|
|
138
|
+
const opsPerSec = durationMs > 0 ? Math.round((iterations / (durationMs / 1000))) : 0
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
passed: true,
|
|
142
|
+
durationMs,
|
|
143
|
+
avgLatencyMs,
|
|
144
|
+
heapBytesUsed,
|
|
145
|
+
opsPerSec,
|
|
146
|
+
iterations,
|
|
147
|
+
error: null,
|
|
148
|
+
summary: `⚡ Profiler: ${iterations} iter in ${durationMs}ms (${opsPerSec.toLocaleString()} ops/sec, ~${Math.round(heapBytesUsed / 1024)}KB heap).`,
|
|
149
|
+
}
|
|
150
|
+
} catch (err) {
|
|
151
|
+
const isTimeout = err.code === 'ERR_SCRIPT_EXECUTION_TIMEOUT' || err.message?.includes('timed out')
|
|
152
|
+
return {
|
|
153
|
+
passed: false,
|
|
154
|
+
durationMs: isTimeout ? timeoutMs : 0,
|
|
155
|
+
avgLatencyMs: 0,
|
|
156
|
+
heapBytesUsed: 0,
|
|
157
|
+
opsPerSec: 0,
|
|
158
|
+
iterations: 0,
|
|
159
|
+
error: isTimeout ? `Execution timed out after ${timeoutMs}ms (possible infinite loop or catastrophic complexity)` : err.message,
|
|
160
|
+
summary: isTimeout ? `🚨 Profiler: TIMEOUT (${timeoutMs}ms exceeded)` : `⚠️ Profiler Error: ${err.message}`,
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Runs runtime performance profiling across all candidate solutions.
|
|
167
|
+
*
|
|
168
|
+
* @param {Array<object>} candidates
|
|
169
|
+
* @param {object} [options]
|
|
170
|
+
* @returns {object} Profiler gate result
|
|
171
|
+
*/
|
|
172
|
+
export function runProfilerGate(candidates, options = {}) {
|
|
173
|
+
const {
|
|
174
|
+
ctx,
|
|
175
|
+
runId,
|
|
176
|
+
onProgress,
|
|
177
|
+
iterations = 100,
|
|
178
|
+
timeoutMs = 2000,
|
|
179
|
+
} = options
|
|
180
|
+
|
|
181
|
+
const results = []
|
|
182
|
+
if (!Array.isArray(candidates) || candidates.length === 0) {
|
|
183
|
+
return {
|
|
184
|
+
results: [],
|
|
185
|
+
fastestCandidate: null,
|
|
186
|
+
profilerDirective: null,
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
191
|
+
const cand = candidates[i]
|
|
192
|
+
const text = typeof cand === 'string' ? cand : cand.output || cand.text || cand.code || ''
|
|
193
|
+
const report = profileCodeExecution(text, { iterations, timeoutMs })
|
|
194
|
+
|
|
195
|
+
if (typeof cand === 'object' && cand !== null) {
|
|
196
|
+
cand.profilerReport = report
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
results.push({
|
|
200
|
+
candidateIndex: i,
|
|
201
|
+
model: cand?.model || `Candidate ${i + 1}`,
|
|
202
|
+
report,
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
if (ctx) {
|
|
206
|
+
emitEvent(ctx, MOA_EVENTS.PROFILER_COMPLETE, {
|
|
207
|
+
runId,
|
|
208
|
+
candidateIndex: i,
|
|
209
|
+
durationMs: report.durationMs,
|
|
210
|
+
heapBytesUsed: report.heapBytesUsed,
|
|
211
|
+
opsPerSec: report.opsPerSec,
|
|
212
|
+
passed: report.passed,
|
|
213
|
+
error: report.error,
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Sort passing candidates by throughput (opsPerSec) descending
|
|
219
|
+
const valid = results.filter((r) => r.report.passed).sort((a, b) => b.report.opsPerSec - a.report.opsPerSec)
|
|
220
|
+
const fastestCandidate = valid[0] || null
|
|
221
|
+
|
|
222
|
+
if (typeof onProgress === 'function' && results.length > 0) {
|
|
223
|
+
const summaryList = results.map((r) => `Ref ${r.candidateIndex + 1}: ${r.report.passed ? `${r.report.durationMs}ms` : 'TIMEOUT'}`).join(', ')
|
|
224
|
+
onProgress(`⚡ *Runtime Profiler Gate*: ${summaryList}\n`)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const profilerDirective = formatProfilerDirective(results)
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
results,
|
|
231
|
+
fastestCandidate,
|
|
232
|
+
profilerDirective,
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Formats profiler benchmarks into a clear prompt directive for the Judge.
|
|
238
|
+
*
|
|
239
|
+
* @param {Array<object>} results
|
|
240
|
+
* @returns {string|null}
|
|
241
|
+
*/
|
|
242
|
+
export function formatProfilerDirective(results) {
|
|
243
|
+
if (!Array.isArray(results) || results.length === 0) return null
|
|
244
|
+
|
|
245
|
+
const lines = [
|
|
246
|
+
'### ⚡ RUNTIME PERFORMANCE & CPU/MEMORY PROFILER REPORT',
|
|
247
|
+
'Objective micro-benchmarks across candidates (100 iterations in isolated sandbox):',
|
|
248
|
+
]
|
|
249
|
+
|
|
250
|
+
for (const r of results) {
|
|
251
|
+
const label = `Candidate ${r.candidateIndex + 1}`
|
|
252
|
+
const rep = r.report
|
|
253
|
+
if (rep.passed) {
|
|
254
|
+
const kb = Math.round(rep.heapBytesUsed / 1024)
|
|
255
|
+
lines.push(
|
|
256
|
+
`- **${label}**: ⏱️ **${rep.durationMs}ms** total (~${rep.avgLatencyMs}ms/op) | **${rep.opsPerSec.toLocaleString()} ops/sec** | Heap: ~${kb} KB`
|
|
257
|
+
)
|
|
258
|
+
} else {
|
|
259
|
+
lines.push(
|
|
260
|
+
`- **${label}**: ❌ **PERFORMANCE FAILURE**: ${rep.error}`
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
lines.push(
|
|
266
|
+
'\n**Judge Performance Guideline**: Penalize solutions with excessive latency or timeout failures. Prefer implementations demonstrating clean algorithmic scalability and minimal memory footprint.'
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
return lines.join('\n')
|
|
270
|
+
}
|