@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 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
+ }
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-moa",
3
- "version": "0.2.76",
3
+ "version": "0.2.77",
4
4
  "description": "Mixture of Agents (MoA) plugin for DeepSeek Harness with /moa slash command",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",