@goodandready/dsh-moa 0.2.73 → 0.2.75

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
@@ -186,6 +186,21 @@ Validates markdown code blocks across all candidate outputs locally using JavaSc
186
186
  ### 20. Early Exit upon Semantic Consensus
187
187
  Compares normalized candidate proposals using token-level Sørensen-Dice similarity. When models arrive at identical or near-identical solutions (meeting or exceeding `early_exit_threshold`, default: `0.95`), the expensive Judge synthesis phase is automatically bypassed. The consensus solution is promoted directly to the workspace, saving up to 100% of judge token costs while accelerating pipeline latency. Configured via `early_exit_consensus_enabled: true` and `early_exit_threshold: 0.95`.
188
188
 
189
+ ### 21. Dynamic Consensus Threshold & Multi-Judge Escalation
190
+ Evaluates pairwise candidate solution divergence (Disagreement Metric, 0.0–1.0) using normalized Sørensen-Dice similarity. When candidate consensus is weak (disagreement meets or exceeds `escalation_disagreement_threshold`, default: `0.40`), the pipeline automatically escalates:
191
+ - `auto_upgrade`: Upgrades from a lightweight or fast aggregator to a designated flagship senior judge (`escalation_judge` or first senior fallback judge).
192
+ - `council_majority`: Convenes a 3-judge panel with majority consensus voting.
193
+ Dispatches `moa/judge-escalation` Cordis events and displays escalation decisions in live stream telemetry. Configured via `escalation_policy: 'single' | 'auto_upgrade' | 'council_majority'`, `escalation_disagreement_threshold: 0.40`, and `escalation_judge`.
194
+
195
+ ### 22. Self-Consistency Temperature Sampling for Single Models
196
+ Allows single-model configurations (such as a local Ollama/vLLM instance or a single API key) to leverage Mixture-of-Agents diversity. When enabled on a preset with 1 model slot, the runner expands it into $N$ parallel candidate streams with distinct temperatures (e.g. 0.2, 0.6, 0.9) and complementary personas (`proposer`, `challenger`, `minimalist`), calculates agreement ratio, and performs full MoA consensus synthesis. Dispatches `moa/self-consistency-expand` Cordis events. Configured via `self_consistency_enabled: true`, `self_consistency_samples: 3`, and `self_consistency_temperatures: [0.2, 0.6, 0.9]`.
197
+
198
+ ### 23. Stack-Aware Dynamic Persona Evolution
199
+ Scans project manifests (`package.json`, `Cargo.toml`, `pyproject.toml`, `requirements.txt`, `go.mod`) to detect languages, frameworks, and libraries. Automatically specializes candidate proposers into domain-specific roles (e.g. React Optimization, TypeScript Safety, Accessibility, FastAPI Async) to maximize hypothesis diversity. Emits `moa/stack-detected` Cordis events. Configured via `stack_aware_personas_enabled: true`.
200
+
201
+ ### 24. Self-Learning Developer Preferences Metaprompt
202
+ Maintains a persistent profile of developer coding principles and architectural preferences in `~/.dsh/moa/preferences.json`. Injects active principles into the Judge prompt to bias consensus toward team standards (strict typing, functional immutability, error boundaries). Manageable via `GET/POST/DELETE /dsh-moa/preferences` REST endpoints and preset toggle `developer_preferences_enabled: true`.
203
+
189
204
  ### 6. Live Canvas 1-Click Preview (optional)
190
205
  If `@goodandready/dsh-live-canvas` is installed in the same profile, `dsh-moa` pushes the promoted HTML file to the Live Canvas REST contract (`POST /dsh-live-canvas/api/preview`, served by the same harness webServer) and appends a one-click preview link (`/dsh-live-canvas/sandbox/<id>`) to the answer. Without the plugin the step is skipped silently — no errors in the log, no dead links.
191
206
 
package/README.ru.md CHANGED
@@ -186,6 +186,21 @@ graph TD
186
186
  ### 20. Ранний выход по семантическому консенсусу
187
187
  Сравнивает нормализованные решения кандидатов по токенному сходству Сёренсена-Дайса. Если решения моделей совпадают или практически идентичны (достигнут порог `early_exit_threshold`, по умолчанию 0.95), стадия вызова судьи (Judge) пропускается. Согласованный вариант немедленно продвигается в проект, экономя до 100% токенов судейской коллегии и сокращая задержку ответа. Управляется параметрами `early_exit_consensus_enabled: true` и `early_exit_threshold: 0.95`.
188
188
 
189
+ ### 21. Динамический порог консенсуса и эскалация судейства (Judge Escalation)
190
+ Оценивает степень расхождения между решениями моделей-кандидатов (метрика Disagreement Metric, 0.0–1.0) на основе попарного расстояния Сёренсена-Дайса. Если расхождение превышает порог (`escalation_disagreement_threshold`, по умолчанию 0.40), пайплайн выполняет адаптивную эскалацию:
191
+ - `auto_upgrade`: автоматический переход с базового/быстрого судьи на флагманскую старшую модель (`escalation_judge` или первый доступный резервный судья).
192
+ - `council_majority`: созыв коллегии из 3 судей с голосованием большинством голосов.
193
+ Генерирует событие `moa/judge-escalation` и выводит статус эскалации в телеметрию стрима.
194
+
195
+ ### 22. Самосогласованное температурное сэмплирование для одиночных моделей (Self-Consistency)
196
+ Позволяет пользователям с единственной моделью (локальная Ollama, vLLM или единственный API-ключ) получать преимущества ансамблевой валидации MoA. Автоматически разворачивает одиночный слот на $N$ параллельных запусков с различными температурами (`0.2, 0.6, 0.9`) и дополняющими друг друга инженерными персонами (`proposer`, `challenger`, `minimalist`), вычисляет коэффициент согласия (Self-Consistency Agreement) и синтезирует итоговый результат. Генерирует событие `moa/self-consistency-expand`.
197
+
198
+ ### 23. Динамическая адаптация персон по манифестам проекта (Stack-Aware Personas)
199
+ Автоматически сканирует манифесты проекта (`package.json`, `Cargo.toml`, `pyproject.toml`, `requirements.txt`, `go.mod`) для определения стека технологий и библиотек. Специализирует роли кандидатов под конкретный стек (оптимизация рендера, строгая типизация, доступность, асинхронные интерфейсы), обеспечивая многоаспектный анализ задачи. Генерирует событие `moa/stack-detected`. Включается через `stack_aware_personas_enabled: true`.
200
+
201
+ ### 24. Метапромпт предпочтений разработчика (Developer Preferences)
202
+ Хранит профиль архитектурных стандартов и стилевых предпочтений разработчика в `~/.dsh/moa/preferences.json`. Инжектирует активные принципы в контекст судьи, гарантируя соответствие выбранного решения вкусам команды (строгая типизация, иммутабельность, обработка ошибок). Доступен REST API (`GET/POST/DELETE /dsh-moa/preferences`) и опция пресета `developer_preferences_enabled: true`.
203
+
189
204
  ### 6. Live Canvas 1-клик предпросмотр (опционально)
190
205
  Если в профиле установлен `@goodandready/dsh-live-canvas`, `dsh-moa` отправляет промоученный HTML-файл в REST-контракт Live Canvas (`POST /dsh-live-canvas/api/preview`, тот же webServer харнесса) и добавляет к ответу ссылку на предпросмотр (`/dsh-live-canvas/sandbox/<id>`). Без плагина шаг пропускается тихо — без ошибок в журнале и без битых ссылок.
191
206
 
package/README.zh.md CHANGED
@@ -163,6 +163,21 @@ graph TD
163
163
  ### 20. 语义共识提前退出机制 (Early Exit upon Semantic Consensus)
164
164
  基于 Token 级 Sørensen-Dice 相似度算法比对归一化后的候选方案。当多个候选模型给出高度一致的代码方案(达到或超过 `early_exit_threshold` 阈值,默认 0.95)时,自动跳过高耗费的裁判综合评审阶段,直接将共识方案提升至项目,在大幅缩短响应延迟的同时节省多达 100% 的裁判 Token 支出。通过 `early_exit_consensus_enabled: true` 与 `early_exit_threshold: 0.95` 配置。
165
165
 
166
+ ### 21. 动态共识阈值与裁判自适应升级 (Dynamic Consensus & Judge Escalation)
167
+ 基于归一化 Sørensen-Dice 计算候选方案间的分歧度 (Disagreement Metric 0.0–1.0)。当候选分歧超过设定阈值 (`escalation_disagreement_threshold`,默认 0.40) 时,流水线自动触发升级策略:
168
+ - `auto_upgrade`: 自动将普通/快速裁判升级为指定的高阶旗舰裁判 (`escalation_judge` 或首选候补裁判)。
169
+ - `council_majority`: 组建 3 裁判多数席裁决团。
170
+ 触发 `moa/judge-escalation` 事件并在实时流中呈现决策依据。
171
+
172
+ ### 22. 单模型自洽性温度采样 (Self-Consistency Temperature Sampling)
173
+ 专为仅配置单模型(如本地 Ollama/vLLM 或单一 API Key)的场景设计。开启后自动将单槽位扩展为 $N$ 个并行采样(分别采用 0.2、0.6、0.9 等递增温度与 `proposer`、`challenger`、`minimalist` 互补提示词),评估自洽一致性比率并进行全流程 MoA 综合聚合,触发 `moa/self-consistency-expand` 事件。
174
+
175
+ ### 23. 基于项目清单的感知角色演化 (Stack-Aware Personas)
176
+ 自动扫描工作区清单 (`package.json`, `Cargo.toml`, `pyproject.toml`, `go.mod`) 识别编程语言、框架与核心依赖,动态为各候选槽位分派互补的领域专家角色(如 React 渲染优化、TypeScript 严格类型安全、可访问性审查、FastAPI 异步架构),触发 `moa/stack-detected` 事件。通过 `stack_aware_personas_enabled: true` 配置。
177
+
178
+ ### 24. 自学习开发者代码偏好元提示词 (Developer Preferences)
179
+ 在本地 `~/.dsh/moa/preferences.json` 持久化存储团队编码规范与架构偏好,将启用的规则动态注入裁判评审阶段,使综合方案贴合开发者个人习惯(严格类型、函数不可变性、防御性错误边界)。提供 `GET/POST/DELETE /dsh-moa/preferences` REST 端点,通过 `developer_preferences_enabled: true` 开启。
180
+
166
181
  ### 6. Live Canvas 一键预览(可选)
167
182
  若同一 profile 中安装了 `@goodandready/dsh-live-canvas`,`dsh-moa` 会将提升后的 HTML 文件推送到 Live Canvas 的 REST 契约(`POST /dsh-live-canvas/api/preview`,由同一 harness webServer 提供服务),并在回答中附上一键预览链接(`/dsh-live-canvas/sandbox/<id>`)。未安装该插件时此步骤静默跳过——日志无报错,也不会出现死链接。
168
183
 
package/lib/client.js CHANGED
@@ -213,7 +213,22 @@ window.__ModuleLoader__.load({
213
213
  "workbench.promote_btn": "Promote Harmonized Files to Workspace",
214
214
  "workbench.promoted_ok": "\u2705 Harmonized deliverable promoted to workspace root!",
215
215
  "workbench.pros": "Pros:",
216
- "workbench.cons": "Cons:"
216
+ "workbench.cons": "Cons:",
217
+ "aggregator.self_consistency_title": "\u{1F3B2} Self-Consistency Temperature Sampling (Single-Model Diversity)",
218
+ "aggregator.self_consistency_desc": "Expands a single reference model into multiple parallel samples with varied temperatures and personas, bringing MoA benefits to single-model setups.",
219
+ "aggregator.self_consistency_samples": "Samples:",
220
+ "aggregator.escalation_title": "\u2696\uFE0F Dynamic Consensus Threshold & Judge Escalation",
221
+ "aggregator.escalation_desc": "Detects divergence across candidates. When consensus is weak, automatically upgrades to a senior flagship judge or convenes a 3-judge panel.",
222
+ "aggregator.escalation_policy": "Escalation Policy:",
223
+ "aggregator.escalation_policy_single": "Standard Single Judge (No Escalation)",
224
+ "aggregator.escalation_policy_upgrade": "Auto-Upgrade to Senior Judge on Divergence",
225
+ "aggregator.escalation_policy_council": "Convene 3-Judge Majority Council on Divergence",
226
+ "aggregator.escalation_threshold": "Divergence Threshold:",
227
+ "aggregator.escalation_judge_label": "Senior Judge:",
228
+ "aggregator.stack_personas_title": "\u{1F4E6} Stack-Aware Dynamic Persona Evolution (Project Manifests)",
229
+ "aggregator.stack_personas_desc": "Scans project manifests (package.json, Cargo.toml, pyproject.toml, go.mod) and assigns specialized engineering roles to candidate slots.",
230
+ "aggregator.preferences_title": "\u{1F9E0} Developer Coding Principles & Preferences Metaprompt",
231
+ "aggregator.preferences_desc": "Injects learned developer preferences (strict typing, functional immutability, error boundaries) to guide Judge synthesis."
217
232
  };
218
233
  const zh = {
219
234
  title: "\u667A\u80FD\u4F53\u6DF7\u5408 (MoA)",
@@ -417,7 +432,22 @@ window.__ModuleLoader__.load({
417
432
  "workbench.promote_btn": "\u63D0\u5347\u7C98\u5408\u7ED3\u679C\u81F3\u9879\u76EE\u6839\u76EE\u5F55",
418
433
  "workbench.promoted_ok": "\u2705 \u7C98\u5408\u65B9\u6848\u5DF2\u6210\u529F\u63D0\u5347\u81F3\u9879\u76EE\u5DE5\u4F5C\u533A\uFF01",
419
434
  "workbench.pros": "\u4F18\u52BF\uFF1A",
420
- "workbench.cons": "\u4E0D\u8DB3\uFF1A"
435
+ "workbench.cons": "\u4E0D\u8DB3\uFF1A",
436
+ "aggregator.self_consistency_title": "\u{1F3B2} \u5355\u6A21\u578B\u81EA\u6D3D\u6027\u6E29\u5EA6\u91C7\u6837 (Self-Consistency)",
437
+ "aggregator.self_consistency_desc": "\u5728\u4EC5\u914D\u7F6E\u5355\u6A21\u578B\u65F6\uFF0C\u751F\u6210\u4E0D\u540C\u6E29\u5EA6\u4E0E\u89D2\u8272\u7684\u591A\u4E2A\u5E76\u884C\u5019\u9009\u65B9\u6848\uFF0C\u8BA9\u672C\u5730 Ollama \u6216\u5355 Key \u4E5F\u4EAB\u6709 MoA \u805A\u5408\u4F18\u52BF\u3002",
438
+ "aggregator.self_consistency_samples": "\u91C7\u6837\u6570:",
439
+ "aggregator.escalation_title": "\u2696\uFE0F \u52A8\u6001\u5171\u8BC6\u9608\u503C\u4E0E\u88C1\u5224\u5347\u7EA7 (Judge Escalation)",
440
+ "aggregator.escalation_desc": "\u68C0\u6D4B\u5019\u9009\u65B9\u6848\u95F4\u7684\u5206\u6B67\u5EA6\u3002\u5F53\u5171\u8BC6\u8F83\u5F31\u65F6\uFF0C\u81EA\u52A8\u5347\u7EA7\u81F3\u9AD8\u9636\u65D7\u8230\u88C1\u5224\u6216\u7EC4\u5EFA 3 \u88C1\u5224\u591A\u6570\u5E2D\u3002",
441
+ "aggregator.escalation_policy": "\u5347\u7EA7\u7B56\u7565:",
442
+ "aggregator.escalation_policy_single": "\u6807\u51C6\u5355\u88C1\u5224 (\u4E0D\u5347\u7EA7)",
443
+ "aggregator.escalation_policy_upgrade": "\u5206\u6B67\u65F6\u81EA\u52A8\u5347\u7EA7\u81F3\u9AD8\u9636\u88C1\u5224",
444
+ "aggregator.escalation_policy_council": "\u5206\u6B67\u65F6\u7EC4\u5EFA 3 \u88C1\u5224\u591A\u6570\u51B3\u7B56\u5E2D",
445
+ "aggregator.escalation_threshold": "\u5206\u6B67\u9608\u503C:",
446
+ "aggregator.escalation_judge_label": "\u9AD8\u9636\u88C1\u5224\u6A21\u578B:",
447
+ "aggregator.stack_personas_title": "\u{1F4E6} \u57FA\u4E8E\u9879\u76EE\u914D\u7F6E\u6E05\u5355\u7684\u611F\u77E5\u89D2\u8272\u6F14\u5316 (Stack-Aware Personas)",
448
+ "aggregator.stack_personas_desc": "\u81EA\u52A8\u626B\u63CF\u9879\u76EE\u6E05\u5355 (package.json, Cargo.toml, pyproject.toml, go.mod) \u5E76\u4E3A\u5404\u5019\u9009\u69FD\u4F4D\u6307\u6D3E\u4E13\u5C5E\u4E13\u4E1A\u89D2\u8272\u3002",
449
+ "aggregator.preferences_title": "\u{1F9E0} \u81EA\u5B66\u4E60\u5F00\u53D1\u8005\u4EE3\u7801\u504F\u597D\u5143\u63D0\u793A\u8BCD (Developer Preferences)",
450
+ "aggregator.preferences_desc": "\u5C06\u5F00\u53D1\u8005\u504F\u597D\uFF08\u4E25\u683C\u7C7B\u578B\u3001\u51FD\u6570\u4E0D\u53EF\u53D8\u6027\u3001\u9632\u5FA1\u6027\u9519\u8BEF\u5904\u7406\u7B49\uFF09\u6CE8\u5165\u88C1\u5224\u7EFC\u5408\u8BC4\u5BA1\u9636\u6BB5\u3002"
421
451
  };
422
452
  function makeT(dict, fallback) {
423
453
  return function t(key, vars) {
@@ -2332,6 +2362,7 @@ window.__ModuleLoader__.load({
2332
2362
  )
2333
2363
  )
2334
2364
  ),
2365
+ // --- src/client/86-card-aggregator-ext2.js ---
2335
2366
  /* Pre-Judge Local Syntax Gate (#303) & Early Semantic Consensus (#299) */
2336
2367
  React.createElement(
2337
2368
  "div",
@@ -2427,6 +2458,158 @@ window.__ModuleLoader__.load({
2427
2458
  t("aggregator.semantic_cache_desc") || "Persists intermediate candidate outputs locally by deterministic SHA-256 fingerprint, eliminating duplicate API costs on retry or repeat requests."
2428
2459
  )
2429
2460
  ),
2461
+ /* Self-Consistency Temperature Sampling (#301) */
2462
+ React.createElement(
2463
+ "div",
2464
+ { 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 } },
2465
+ React.createElement(
2466
+ "label",
2467
+ { style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 13, fontWeight: 500 } },
2468
+ React.createElement("input", {
2469
+ type: "checkbox",
2470
+ checked: Boolean(currentPreset.self_consistency_enabled),
2471
+ onChange: (e) => updateCurrentPreset((p) => ({ ...p, self_consistency_enabled: e.target.checked })),
2472
+ style: { accentColor: "var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))", cursor: "pointer" }
2473
+ }),
2474
+ t("aggregator.self_consistency_title") || "\u{1F3B2} Self-Consistency Temperature Sampling (Single-Model Diversity)"
2475
+ ),
2476
+ React.createElement(
2477
+ "div",
2478
+ { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary)", marginLeft: 22 } },
2479
+ t("aggregator.self_consistency_desc") || "Expands a single reference model into multiple parallel samples with varied temperatures and personas, bringing MoA benefits to single-model setups."
2480
+ ),
2481
+ currentPreset.self_consistency_enabled && React.createElement(
2482
+ "div",
2483
+ { style: { marginLeft: 22, marginTop: 4, display: "flex", alignItems: "center", gap: 12 } },
2484
+ React.createElement("span", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary)" } }, (t("aggregator.self_consistency_samples") || "Samples:") + " " + (currentPreset.self_consistency_samples || 3)),
2485
+ React.createElement("input", {
2486
+ type: "range",
2487
+ min: 2,
2488
+ max: 5,
2489
+ value: currentPreset.self_consistency_samples || 3,
2490
+ onChange: (e) => updateCurrentPreset((p) => ({ ...p, self_consistency_samples: Number(e.target.value) })),
2491
+ style: { width: 100, accentColor: "var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))" }
2492
+ })
2493
+ )
2494
+ ),
2495
+ /* Dynamic Consensus Threshold & Multi-Judge Escalation (#300) */
2496
+ React.createElement(
2497
+ "div",
2498
+ { 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 } },
2499
+ React.createElement(
2500
+ "div",
2501
+ { style: { fontSize: 13, fontWeight: 600, color: "var(--dsw-alias-label-primary)" } },
2502
+ t("aggregator.escalation_title") || "\u2696\uFE0F Dynamic Consensus Threshold & Judge Escalation"
2503
+ ),
2504
+ React.createElement(
2505
+ "div",
2506
+ { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary)" } },
2507
+ t("aggregator.escalation_desc") || "Detects divergence across candidates. When consensus is weak, automatically upgrades to a senior flagship judge or convenes a 3-judge panel."
2508
+ ),
2509
+ React.createElement(
2510
+ "div",
2511
+ { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 4 } },
2512
+ React.createElement("span", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary)" } }, t("aggregator.escalation_policy") || "Escalation Policy:"),
2513
+ React.createElement(
2514
+ "select",
2515
+ {
2516
+ value: currentPreset.escalation_policy || "single",
2517
+ onChange: (e) => updateCurrentPreset((p) => ({ ...p, escalation_policy: e.target.value })),
2518
+ style: {
2519
+ padding: "4px 8px",
2520
+ fontSize: 12,
2521
+ borderRadius: 6,
2522
+ border: "1px solid var(--dsw-alias-border-l2)",
2523
+ background: "var(--dsw-alias-bg-layer-1)",
2524
+ color: "var(--dsw-alias-label-primary)"
2525
+ }
2526
+ },
2527
+ React.createElement("option", { value: "single" }, t("aggregator.escalation_policy_single") || "Standard Single Judge (No Escalation)"),
2528
+ React.createElement("option", { value: "auto_upgrade" }, t("aggregator.escalation_policy_upgrade") || "Auto-Upgrade to Senior Judge on Divergence"),
2529
+ React.createElement("option", { value: "council_majority" }, t("aggregator.escalation_policy_council") || "Convene 3-Judge Majority Council on Divergence")
2530
+ )
2531
+ ),
2532
+ currentPreset.escalation_policy && currentPreset.escalation_policy !== "single" && React.createElement(
2533
+ "div",
2534
+ { style: { display: "flex", flexDirection: "column", gap: 6, marginTop: 4 } },
2535
+ React.createElement(
2536
+ "div",
2537
+ { style: { display: "flex", alignItems: "center", gap: 12 } },
2538
+ React.createElement("span", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary)" } }, (t("aggregator.escalation_threshold") || "Divergence Threshold:") + " " + Math.round((currentPreset.escalation_disagreement_threshold ?? 0.4) * 100) + "%"),
2539
+ React.createElement("input", {
2540
+ type: "range",
2541
+ min: 10,
2542
+ max: 90,
2543
+ value: Math.round((currentPreset.escalation_disagreement_threshold ?? 0.4) * 100),
2544
+ onChange: (e) => updateCurrentPreset((p) => ({ ...p, escalation_disagreement_threshold: Number(e.target.value) / 100 })),
2545
+ style: { width: 120, accentColor: "var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))" }
2546
+ })
2547
+ ),
2548
+ currentPreset.escalation_policy === "auto_upgrade" && React.createElement(
2549
+ "div",
2550
+ { style: { display: "flex", alignItems: "center", gap: 8 } },
2551
+ React.createElement("span", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary)" } }, t("aggregator.escalation_judge_label") || "Senior Judge:"),
2552
+ React.createElement(
2553
+ "div",
2554
+ { style: { flex: 1, maxWidth: 300 } },
2555
+ React.createElement(SearchableModelPicker, {
2556
+ provider: currentPreset.escalation_judge?.provider || "",
2557
+ model: currentPreset.escalation_judge?.model || "",
2558
+ onChange: (prov, mod) => {
2559
+ updateCurrentPreset((p) => ({
2560
+ ...p,
2561
+ escalation_judge: { provider: prov, model: mod }
2562
+ }));
2563
+ },
2564
+ availableModels,
2565
+ t
2566
+ })
2567
+ )
2568
+ )
2569
+ )
2570
+ ),
2571
+ /* Stack-Aware Dynamic Persona Evolution (#308) */
2572
+ React.createElement(
2573
+ "div",
2574
+ { 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 } },
2575
+ React.createElement(
2576
+ "label",
2577
+ { style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 13, fontWeight: 500 } },
2578
+ React.createElement("input", {
2579
+ type: "checkbox",
2580
+ checked: Boolean(currentPreset.stack_aware_personas_enabled),
2581
+ onChange: (e) => updateCurrentPreset((p) => ({ ...p, stack_aware_personas_enabled: e.target.checked })),
2582
+ style: { accentColor: "var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))", cursor: "pointer" }
2583
+ }),
2584
+ t("aggregator.stack_personas_title") || "\u{1F4E6} Stack-Aware Dynamic Persona Evolution (Project Manifests)"
2585
+ ),
2586
+ React.createElement(
2587
+ "div",
2588
+ { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary)", marginLeft: 22 } },
2589
+ t("aggregator.stack_personas_desc") || "Scans project manifests (package.json, Cargo.toml, pyproject.toml, go.mod) and assigns specialized engineering roles to candidate slots."
2590
+ )
2591
+ ),
2592
+ /* Self-Learning Developer Preferences Metaprompt (#309) */
2593
+ React.createElement(
2594
+ "div",
2595
+ { 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 } },
2596
+ React.createElement(
2597
+ "label",
2598
+ { style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 13, fontWeight: 500 } },
2599
+ React.createElement("input", {
2600
+ type: "checkbox",
2601
+ checked: Boolean(currentPreset.developer_preferences_enabled),
2602
+ onChange: (e) => updateCurrentPreset((p) => ({ ...p, developer_preferences_enabled: e.target.checked })),
2603
+ style: { accentColor: "var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))", cursor: "pointer" }
2604
+ }),
2605
+ t("aggregator.preferences_title") || "\u{1F9E0} Developer Coding Principles & Preferences Metaprompt"
2606
+ ),
2607
+ React.createElement(
2608
+ "div",
2609
+ { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary)", marginLeft: 22 } },
2610
+ t("aggregator.preferences_desc") || "Injects learned developer preferences (strict typing, functional immutability, error boundaries) to guide Judge synthesis."
2611
+ )
2612
+ ),
2430
2613
  /* Fallback Judges Chain */
2431
2614
  React.createElement(
2432
2615
  "div",
package/lib/events.js CHANGED
@@ -7,6 +7,10 @@ export const MOA_EVENTS = {
7
7
  CANDIDATE_COMPLETE: 'moa/candidate-complete',
8
8
  SYNTAX_VALIDATION: 'moa/syntax-validation',
9
9
  EARLY_CONSENSUS: 'moa/early-consensus',
10
+ JUDGE_ESCALATION: 'moa/judge-escalation',
11
+ SELF_CONSISTENCY_EXPAND: 'moa/self-consistency-expand',
12
+ STACK_DETECTED: 'moa/stack-detected',
13
+ PREFERENCES_APPLIED: 'moa/preferences-applied',
10
14
  CRITIQUE_COMPLETE: 'moa/critique-complete',
11
15
  JUDGE_VERDICT: 'moa/judge-verdict',
12
16
  CONTEXT_PRUNING: 'moa/context-pruning',
package/lib/moa-schema.js CHANGED
@@ -83,6 +83,14 @@ export const PresetSchema = z.object({
83
83
  context_pruning_diff_lines: z.number().default(5),
84
84
  semantic_cache_enabled: z.boolean().default(false),
85
85
  semantic_cache_ttl_hours: z.number().default(168),
86
+ self_consistency_enabled: z.boolean().default(false),
87
+ self_consistency_samples: z.number().default(3),
88
+ self_consistency_temperatures: z.array(z.number()).default([0.2, 0.6, 0.9]),
89
+ escalation_policy: z.string().default('single'),
90
+ escalation_disagreement_threshold: z.number().default(0.40),
91
+ escalation_judge: ModelSlotSchema.default({ provider: '', model: '' }),
92
+ stack_aware_personas_enabled: z.boolean().default(false),
93
+ developer_preferences_enabled: z.boolean().default(false),
86
94
  })
87
95
 
88
96
  export const Config = z.object({
@@ -112,6 +120,14 @@ export const Config = z.object({
112
120
  context_pruning_diff_lines: z.number().default(5).volatile(),
113
121
  semantic_cache_enabled: z.boolean().default(false).volatile(),
114
122
  semantic_cache_ttl_hours: z.number().default(168).volatile(),
123
+ self_consistency_enabled: z.boolean().default(false).volatile(),
124
+ self_consistency_samples: z.number().default(3).volatile(),
125
+ self_consistency_temperatures: z.array(z.number()).default([0.2, 0.6, 0.9]).volatile(),
126
+ escalation_policy: z.string().default('single').volatile(),
127
+ escalation_disagreement_threshold: z.number().default(0.40).volatile(),
128
+ escalation_judge: ModelSlotSchema.default({ provider: '', model: '' }).volatile(),
129
+ stack_aware_personas_enabled: z.boolean().default(false).volatile(),
130
+ developer_preferences_enabled: z.boolean().default(false).volatile(),
115
131
  smart_routing_model: ModelSlotSchema.default({ provider: '', model: '' }).volatile(),
116
132
  prices: z.dict(PriceRow).default({}).volatile(),
117
133
  presets: z.array(PresetSchema).default([
@@ -1,3 +1,7 @@
1
+ import { detectProjectStack, assignStackAwarePersonas, formatStackAwareDirective } from './manifest-stack.js'
2
+ import { formatPreferencesDirective } from './developer-preferences.js'
3
+ import { expandSelfConsistencySlots } from './self-consistency.js'
4
+ import { emitEvent, MOA_EVENTS } from '../events.js'
1
5
  import { balanceCandidateQuotas } from '../moa-quota-balancer.js'
2
6
  import { collectProjectContext, formatProjectContext, isRefinementTask } from '../workspace/scanner.js'
3
7
  import { applyBudgetGuardrails, createBudgetAbortPayload } from '../moa-budget.js'
@@ -27,7 +31,40 @@ export async function stageProjectContext({
27
31
  const refTemp = typeof preset?.reference_temperature === 'number' ? preset.reference_temperature : 0.6
28
32
  const aggTemp = typeof preset?.aggregator_temperature === 'number' ? preset.aggregator_temperature : 0.4
29
33
  const judgeCriteria = preset?.judge_criteria || ''
30
- let isFastMode = referenceModels.length === 1 && !preset?.curator_synthesis
34
+ const selfConsistencyEnabled = Boolean(preset?.self_consistency_enabled)
35
+ const selfConsistencySamples = typeof preset?.self_consistency_samples === 'number' ? preset.self_consistency_samples : 3
36
+ const selfConsistencyTemperatures = Array.isArray(preset?.self_consistency_temperatures)
37
+ ? preset.self_consistency_temperatures
38
+ : [0.2, 0.6, 0.9]
39
+ const escalationPolicy = preset?.escalation_policy || 'single'
40
+ const escalationDisagreementThreshold = typeof preset?.escalation_disagreement_threshold === 'number'
41
+ ? preset.escalation_disagreement_threshold
42
+ : 0.40
43
+ const escalationJudge = (preset?.escalation_judge?.model) ? preset.escalation_judge : null
44
+
45
+ let effectiveReferenceModels = referenceModels
46
+ let selfConsistencyExpanded = false
47
+ if (selfConsistencyEnabled) {
48
+ const scRes = expandSelfConsistencySlots(referenceModels, {
49
+ enabled: true,
50
+ samples: selfConsistencySamples,
51
+ temperatures: selfConsistencyTemperatures,
52
+ })
53
+ if (scRes.isExpanded) {
54
+ effectiveReferenceModels = scRes.references
55
+ selfConsistencyExpanded = true
56
+ emitEvent(ctx, MOA_EVENTS.SELF_CONSISTENCY_EXPAND, {
57
+ originalModel: referenceModels[0]?.model,
58
+ sampleCount: scRes.sampleCount,
59
+ temperatures: selfConsistencyTemperatures.slice(0, scRes.sampleCount),
60
+ })
61
+ if (typeof onProgress === 'function') {
62
+ onProgress(`🎲 *Self-Consistency: Expanded single model into ${scRes.sampleCount} diverse temperature samples (${selfConsistencyTemperatures.slice(0, scRes.sampleCount).join(', ')})*\n`)
63
+ }
64
+ }
65
+ }
66
+
67
+ let isFastMode = effectiveReferenceModels.length === 1 && !preset?.curator_synthesis
31
68
  const isCuratorSynthesis = Boolean(preset?.curator_synthesis)
32
69
  const isStreamAggregator = preset?.stream_aggregator !== false
33
70
  const isQuorumEnabled = Boolean(preset?.quorum_enabled)
@@ -46,6 +83,34 @@ export async function stageProjectContext({
46
83
  const semanticCacheEnabled = Boolean(preset?.semantic_cache_enabled)
47
84
  const semanticCacheTtlHours = typeof preset?.semantic_cache_ttl_hours === 'number' ? preset.semantic_cache_ttl_hours : 168
48
85
 
86
+ const stackInfo = detectProjectStack(cwd)
87
+ if (stackInfo.manifestFound) {
88
+ emitEvent(ctx, MOA_EVENTS.STACK_DETECTED, {
89
+ primaryLanguage: stackInfo.primaryLanguage,
90
+ frameworks: stackInfo.frameworks,
91
+ libraries: stackInfo.libraries,
92
+ stackSummary: stackInfo.stackSummary,
93
+ })
94
+ if (typeof onProgress === 'function' && stackInfo.stackSummary) {
95
+ onProgress(`📦 *Project Stack: Detected ${stackInfo.stackSummary}*\n`)
96
+ }
97
+ }
98
+
99
+ const stackAwarePersonasEnabled = Boolean(preset?.stack_aware_personas_enabled)
100
+ if (stackAwarePersonasEnabled && !selfConsistencyExpanded) {
101
+ const stackRes = assignStackAwarePersonas(effectiveReferenceModels, stackInfo, { enabled: true })
102
+ if (stackRes.isSpecialized) {
103
+ effectiveReferenceModels = stackRes.references
104
+ }
105
+ }
106
+
107
+ const developerPreferencesEnabled = Boolean(preset?.developer_preferences_enabled)
108
+ const developerPreferencesDirective = formatPreferencesDirective(null, { enabled: developerPreferencesEnabled })
109
+ if (developerPreferencesDirective) {
110
+ emitEvent(ctx, MOA_EVENTS.PREFERENCES_APPLIED, { enabled: true })
111
+ }
112
+ const stackAwareDirective = formatStackAwareDirective(stackInfo)
113
+
49
114
  const collectedCtx = await collectProjectContext(cwd, 16000)
50
115
  const isRefinement = Boolean(collectedCtx?.files?.length > 0 && isRefinementTask(userPrompt, collectedCtx.files))
51
116
  let projectContext = ''
@@ -65,7 +130,7 @@ export async function stageProjectContext({
65
130
  }
66
131
 
67
132
  const budgetCheck = applyBudgetGuardrails({
68
- references: referenceModels,
133
+ references: effectiveReferenceModels,
69
134
  enabled: preset?.budget_guard_enabled,
70
135
  maxBudgetUsd: preset?.max_budget_usd,
71
136
  action: preset?.budget_action || 'trim',
@@ -133,6 +198,9 @@ export async function stageProjectContext({
133
198
  localPreflightCheck, earlyExitConsensusEnabled, earlyExitThreshold,
134
199
  contextPruningEnabled, contextPruningDiffLines,
135
200
  semanticCacheEnabled, semanticCacheTtlHours,
201
+ selfConsistencyEnabled, selfConsistencyExpanded, selfConsistencySamples, selfConsistencyTemperatures,
202
+ escalationPolicy, escalationDisagreementThreshold, escalationJudge,
203
+ stackInfo, stackAwareDirective, developerPreferencesDirective,
136
204
  }
137
205
  }
138
206
 
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Self-Learning Developer Preferences Metaprompt (#309)
3
+ *
4
+ * Tracks developer coding principles, architectural standards, and preferred idioms
5
+ * in a persistent local store (~/.dsh/moa/preferences.json).
6
+ * Injects a concise "Developer Coding Principles" directive into the Judge
7
+ * synthesis phase (and candidate prompts) to bias consensus toward the engineer's tastes.
8
+ */
9
+
10
+ import fs from 'node:fs'
11
+ import path from 'node:path'
12
+ import os from 'node:os'
13
+
14
+ export const DEFAULT_PREFERENCES = Object.freeze([
15
+ {
16
+ id: 'strict-typing',
17
+ title: 'Strict Types & Explicit Contracts',
18
+ rule: "Never use loose 'any' types or unchecked type casts. Explicitly define interfaces, return types, and input validation schemas.",
19
+ category: 'typing',
20
+ enabled: true,
21
+ },
22
+ {
23
+ id: 'functional-purity',
24
+ title: 'Functional Immutability',
25
+ rule: 'Prefer pure functions, immutable data transforms, and explicit return values over hidden global state mutations.',
26
+ category: 'architecture',
27
+ enabled: true,
28
+ },
29
+ {
30
+ id: 'defensive-errors',
31
+ title: 'Defensive Error Handling',
32
+ rule: 'Never swallow exceptions silently. Use structured error types, descriptive messages, and clean fallback strategies.',
33
+ category: 'reliability',
34
+ enabled: true,
35
+ },
36
+ ])
37
+
38
+ /**
39
+ * Resolves the persistent preferences file path.
40
+ *
41
+ * @param {string} [customPath]
42
+ * @returns {string}
43
+ */
44
+ export function getPreferencesFilePath(customPath = null) {
45
+ if (customPath && typeof customPath === 'string' && customPath.trim()) {
46
+ return path.resolve(customPath.trim())
47
+ }
48
+ return path.join(os.homedir(), '.dsh', 'moa', 'preferences.json')
49
+ }
50
+
51
+ /**
52
+ * Loads developer preferences from disk with default fallback.
53
+ *
54
+ * @param {string} [customPath]
55
+ * @returns {Array<object>}
56
+ */
57
+ export function loadDeveloperPreferences(customPath = null) {
58
+ const filePath = getPreferencesFilePath(customPath)
59
+ if (!fs.existsSync(filePath)) {
60
+ // Seed default preferences file
61
+ try {
62
+ const dir = path.dirname(filePath)
63
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
64
+ fs.writeFileSync(filePath, JSON.stringify(DEFAULT_PREFERENCES, null, 2), 'utf8')
65
+ } catch {
66
+ // safe fallback if filesystem is read-only
67
+ }
68
+ return [...DEFAULT_PREFERENCES]
69
+ }
70
+
71
+ try {
72
+ const raw = fs.readFileSync(filePath, 'utf8')
73
+ const parsed = JSON.parse(raw)
74
+ if (Array.isArray(parsed)) {
75
+ return parsed.filter((p) => p && typeof p === 'object' && p.id && p.rule)
76
+ }
77
+ } catch {
78
+ // safe fallback
79
+ }
80
+
81
+ return [...DEFAULT_PREFERENCES]
82
+ }
83
+
84
+ /**
85
+ * Atomically saves developer preferences to disk.
86
+ *
87
+ * @param {Array<object>} preferences
88
+ * @param {string} [customPath]
89
+ * @returns {boolean}
90
+ */
91
+ export function saveDeveloperPreferences(preferences, customPath = null) {
92
+ if (!Array.isArray(preferences)) return false
93
+ const filePath = getPreferencesFilePath(customPath)
94
+
95
+ try {
96
+ const dir = path.dirname(filePath)
97
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
98
+ const tmp = `${filePath}.tmp.${Date.now()}`
99
+ fs.writeFileSync(tmp, JSON.stringify(preferences, null, 2), 'utf8')
100
+ fs.renameSync(tmp, filePath)
101
+ return true
102
+ } catch {
103
+ return false
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Adds a new developer preference rule.
109
+ *
110
+ * @param {object} item - { title, rule, category, enabled }
111
+ * @param {string} [customPath]
112
+ * @returns {object|null}
113
+ */
114
+ export function addDeveloperPreference(item = {}, customPath = null) {
115
+ if (!item || !item.rule || typeof item.rule !== 'string') return null
116
+ const current = loadDeveloperPreferences(customPath)
117
+
118
+ const id = item.id || `pref-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`
119
+ const newPref = {
120
+ id,
121
+ title: (item.title && String(item.title).trim()) || 'Custom Coding Standard',
122
+ rule: String(item.rule).trim(),
123
+ category: (item.category && String(item.category).trim()) || 'general',
124
+ enabled: item.enabled !== false,
125
+ createdAt: new Date().toISOString(),
126
+ }
127
+
128
+ current.push(newPref)
129
+ saveDeveloperPreferences(current, customPath)
130
+ return newPref
131
+ }
132
+
133
+ /**
134
+ * Updates an existing developer preference.
135
+ *
136
+ * @param {string} id
137
+ * @param {object} updates
138
+ * @param {string} [customPath]
139
+ * @returns {object|null}
140
+ */
141
+ export function updateDeveloperPreference(id, updates = {}, customPath = null) {
142
+ if (!id) return null
143
+ const current = loadDeveloperPreferences(customPath)
144
+ const idx = current.findIndex((p) => p.id === id)
145
+ if (idx === -1) return null
146
+
147
+ const target = current[idx]
148
+ if (typeof updates.title === 'string') target.title = updates.title.trim()
149
+ if (typeof updates.rule === 'string') target.rule = updates.rule.trim()
150
+ if (typeof updates.category === 'string') target.category = updates.category.trim()
151
+ if (typeof updates.enabled === 'boolean') target.enabled = updates.enabled
152
+
153
+ saveDeveloperPreferences(current, customPath)
154
+ return target
155
+ }
156
+
157
+ /**
158
+ * Deletes a developer preference by ID.
159
+ *
160
+ * @param {string} id
161
+ * @param {string} [customPath]
162
+ * @returns {boolean}
163
+ */
164
+ export function deleteDeveloperPreference(id, customPath = null) {
165
+ if (!id) return false
166
+ const current = loadDeveloperPreferences(customPath)
167
+ const filtered = current.filter((p) => p.id !== id)
168
+ if (filtered.length === current.length) return false
169
+
170
+ return saveDeveloperPreferences(filtered, customPath)
171
+ }
172
+
173
+ /**
174
+ * Formats active developer preferences into an injected prompt directive.
175
+ *
176
+ * @param {Array<object>} [preferences] - Array of preferences (loads from disk if omitted)
177
+ * @param {object} [options]
178
+ * @param {boolean} [options.enabled=true]
179
+ * @returns {string|null}
180
+ */
181
+ export function formatPreferencesDirective(preferences = null, options = {}) {
182
+ const { enabled = true, customPath = null } = options
183
+ if (!enabled) return null
184
+
185
+ const list = Array.isArray(preferences) ? preferences : loadDeveloperPreferences(customPath)
186
+ const active = list.filter((p) => p && p.enabled !== false && p.rule)
187
+
188
+ if (active.length === 0) return null
189
+
190
+ const lines = active.map((p) => `- [${p.title}]: ${p.rule}`)
191
+
192
+ return `### Developer Coding Principles & Preferred Conventions
193
+ The developer has established the following coding standards for this project:
194
+ ${lines.join('\n')}
195
+ Evaluate all proposals against these principles and favor implementations adhering to them.`
196
+ }